SCSSを深堀って設計してみた 2
2025.07.11 09:00
2025.07.10 23:07

前回の続きです。
今回は他のマップも作ってみます。
color-map
:ブランドカラーも入れてみた
色も最初は $red
, $blue
みたいにしてたんだけど、だんだん「この赤ってどの赤?危険用?ボタン用?」みたいになってきたので、フォントの時と同じく、意味ベースの color-map
にしました。
@use './variables' as *;
$color-map: (
// === テキスト系 ===
'text': (
'default': $color-black, // 主に本文テキストやラベルなど(濃いめ)
'sub': $color-gray-600, // サブ情報・補足文など(薄いめ)
'extra-sub': $color-gray-400, // サブ情報・補足文など(薄いめ)
'inverse': $color-white, // ダーク背景上の白文字(反転)
'link': $color-blue, // アンカーリンク
'error': $color-red, // バリデーションエラーや警告文など
'accent': $color-blue, // アクセント文字(CTAなど)
),
// === 背景色 ===
'bg': (
'plain': $color-white,
'default': $color-gray, // 標準背景(ページ全体やベースレイヤーに使用)
'inverse': $color-black, // 反転背景
'inverse-veil': rgba(0, 0, 0, .1),
'subtle': $color-gray-100, // サブ背景(セクション分けやブロックの区切り)
'emphasis': $color-gray-200, // 強調背景(注目させたい領域やカード内背景など)
'label': $color-gray-300, // 強調背景(注目させたい領域やカード内背景など)
'error': $color-red, // エラー表示用背景(バナーやアラートなど)
'accent': $color-blue, // アクセント背景(CTA・強調ブロック・アイキャッチ領域など)
),
// === ボタン系 ===
'btn': (
'primary-bg': $color-blue, // プライマリボタン背景色
'primary-text': $color-white, // プライマリボタン文字色
'secondary-bg': $color-gray-200, // セカンダリボタン背景色
'secondary-text': $color-black, // セカンダリボタン文字色
'error-bg': $color-red, // エラーボタン背景色
'error-text': $color-white // エラーボタン文字色
),
このマップを使うためのmixinも以下のように作ってみました。
// ==================================================
// Mixin/Function: color
// カラーマップから意味ベースで色を取得する
// ==================================================
@use 'sass:map';
@use '../foundation' as *;
@function color($group, $key) {
@return map.get(map.get($color-map, $group), $key);
}
// 例:@include color('text', 'base');
@mixin color($group: 'text', $key: 'default', $property: null) {
// プロパティの自動判定
@if $property == null {
@if $group == 'text' {
$property: color;
} @else if $group == 'bg' {
$property: background-color;
} @else if $group == 'border' {
$property: border-color;
} @else if str-index($key, '-text') {
$property: color;
} @else if str-index($key, '-bg') {
$property: background-color;
} @else {
$property: color; // fallback
}
}
$val: color($group, $key);
@if $val == null {
@warn "[color] No value found for (#{$group}, #{$key})";
} @else {
& {
#{$property}: $val;
}
}
}
使い方はこんな感じです。
// 文字
@include color('text', 'accent');
// 背景
@include color('bg', 'accent');
// ボタン
@include color('button', 'accent');
意味で書けると後からテーマカラーを変えたいときも楽だし、「この色何だっけ問題」が減ります。
ただ、微妙に似てる色が増えてきたので、もう少し整理したいなとも思ってます。
space-map
:余白も意味で書けるようにした
余白も最初は気分で書いてたけど、space-map
を作って管理するようにしました。
Tailwind の考え方に近くて、xs
, sm
, md
, lg
, xl
で書いてます。
@use './function' as *;
$space-map: (
// 無しの場合
'none': (
'sm': rem(0),
'md': rem(0),
'lg': rem(0)
),
// 最小余白・詰めたい箇所に使用
'xs': (
'sm': rem(4),
'md': rem(4),
'lg': rem(4)
),
// 通常よりやや小さめの余白
// 例:アイコンとテキストの間、インライン要素間など
'sm': (
'sm': rem(8),
'md': rem(8),
'lg': rem(8)
),
// 汎用的な中サイズの余白
// 例:カード内の余白、要素の上下余白など
'md': (
'sm': rem(12),
'md': rem(16),
'lg': rem(16)
),
// セクション間の余白などに使いやすいサイズ
'lg': (
'sm': rem(20),
'md': rem(24),
'lg': rem(24)
),
// セクション見出しの下や大きな間隔に使うサイズ
'xl': (
'sm': rem(24),
'md': rem(32),
'lg': rem(32)
),
// HERO内など、広めの余白
'2xl': (
'sm': rem(32),
'md': rem(40),
'lg': rem(40)
),
// セクション区切りなどの大きな余白
'3xl': (
'sm': rem(40),
'md': rem(48),
'lg': rem(48)
),
// ファーストビュー下部など、画面を区切る余白
'4xl': (
'sm': rem(48),
'md': rem(64),
'lg': rem(64)
),
// ページ最下部やHEROの下など特大余白
'5xl': (
'sm': rem(64),
'md': rem(80),
'lg': rem(80)
),
// HERO直後やLP切り替えなど、特大のセクション間余白
'6xl': (
'sm': rem(80),
'md': rem(96),
'lg': rem(112)
),
上記を使うためのspace用mixin。
@use 'sass:string';
@use 'sass:map';
@use '../foundation' as *;
// --------------------------------------------------
// spacing用ショートハンド対応表
// --------------------------------------------------
$spacing-property-map: (
mt: margin-top,
mb: margin-bottom,
ml: margin-left,
mr: margin-right,
pt: padding-top,
pb: padding-bottom,
pl: padding-left,
pr: padding-right,
m: margin,
p: padding,
gap: gap
);
// px/py/mx/my 用の展開マップ
$spacing-composite-map: (
px: (padding-left, padding-right),
py: (padding-top, padding-bottom),
mx: (margin-left, margin-right),
my: (margin-top, margin-bottom)
);
@mixin spacing($direction, $token) {
$key: string.unquote($token);
$property: map.get($spacing-property-map, $direction);
$composite: map.get($spacing-composite-map, $direction);
@if $property == null and $composite == null {
@warn "spacing: 不正な direction '#{$direction}'";
@error "指定された方向 '#{$direction}' は無効です。mt, mb, px, py などを指定してください";
}
@if map.get($space-map, $key) == null {
@warn "spacing: space-map に '#{$key}' が見つかりません";
@error "スペーシングトークン '#{$key}' が space-map に存在しません";
}
$apply-properties: if($composite != null, $composite, ($property,));
@each $prop in $apply-properties {
& {
#{$prop}: map.get(map.get($space-map, $key), sm);
}
@include mq(md) {
& {
#{$prop}: map.get(map.get($space-map, $key), md);
}
}
@include mq(lg) {
& {
#{$prop}: map.get(map.get($space-map, $key), lg);
}
}
}
}
実際にはこんな感じで使います。
// margin
@include spacing('m', 'lg');
// margin-right
@include spacing('ml', 'lg');
// margin-x
@include spacing('mx', 'lg');
// padding-y
@include spacing('py', 'lg');
メディアクエリの書き方
メディアクエリもマップ化できちゃうみたいですね。
これはfoundation/_mq.scssというファイルのみで行います。
// Media Queries
@mixin mq($breakpoint) {
// スマホ以上
@if $breakpoint == sm {
@media (min-width: 480px) { @content; }
// スマホ・タブレット以上
} @else if $breakpoint == md {
@media (min-width: 768px) { @content; }
// PC以上
} @else if $breakpoint == lg {
@media (min-width: 1024px) { @content; }
}
使い方はこんな感じ。
.container {
width: 100%;
@include mq(md) {
// スマホ・タブレット以上にのみ反映させたいCSSを書く
width: 80%;
}
}
モバイルファーストで書いて、必要なときにだけ mq()
で上書きするスタイル。
これも「もっと細かいブレークポイント欲しいかも…」と思うことがあるので、あとで見直すかもしれません。
でも今までは以下のようにブレークポイントごとに書いていたので、
何が何処にあるかわからなくなりやすかったんだけど、
今は要素の中にブレークポイントを指定できるので、
すっごくわかりやすくなりましたね。
@media all and (min-width: 0) and (max-width: 480px) {
という感じで、今回はここまでのメモでした!
書いてみて思ったのは、設計って「一度決めたら終わり」じゃなくて、運用しながら少しずつ直していくものなんだな、ってことですね。
今回は以上です!