SCSSを深堀って設計してみた 1

2025.07.08 09:00
2025.07.08 10:10
SCSSを深堀って設計してみた 1

普通に書いていたつもりのCSSが、いつのまにかぐちゃぐちゃになっていて…
「ちゃんと設計しなきゃダメかも」と思って、いろいろ調べて試してみました。

この記事は、そのときに調べたことと、実際に書いてみたメモです。
font-mapcolor-maprem()のことだけじゃなくて、途中で気づいたことや「これやりすぎかも…」と思ったポイントも書いてます。

本気で設計!みたいな大げさなものじゃなく、あくまで素人なりに頑張ってみた記録です。

はじめに

普通にCSSを書いてるつもりだったのに、気づいたら同じようなコードがあちこちにあって、修正するのも一苦労。「これ、毎回こんなにグチャグチャになるの…なんで?」と思って、ちゃんと設計しないとダメだな、って思いました。

本気の設計といっても、最初から全部わかってたわけじゃなくて、ネットでいろいろ調べたり、人のコードを見て「なるほど」とか「これは無理」とか思いながら少しずつ整えていった感じです。

ここに書いてるのは、そのときのメモです。
自分用の備忘録みたいなものなので、たぶん正解とは限らないけど、同じように悩んでる人には何かヒントになるかもしれません。

なんで「設計しなきゃ」と思ったのか

最初は本当に適当に書いてました。
たとえばボタンのスタイルを .btn に書いて、ちょっと違うデザインのボタンが出てきたら .btn2 とか、.btn-special とか無限にクラスが増えていく。
余白のサイズも、気分で 10px とか 12px とか書いていて、後で修正するのがめちゃくちゃ面倒でした。

特にしんどかったのは、レスポンシブ対応。
スマホ用のスタイルが別ファイルに散らばっていたり、そもそもどのブレークポイントで何をしてるのかわからなくなって、途中で諦めたこともあります。

そんなとき、たまたま別の人が作ったSCSSのプロジェクトを見せてもらったら、フォルダもクラス名もすごく整っていて、ブレークポイントも意味のある名前で管理されていて、「これだ…!」と思ったのがきっかけです。

調べたこととやってみたこと

FLOCSSとかBEMとか…どれにするか迷った話

「設計するなら、まずは設計思想を決めるらしいぞ」ということを知って、いろいろ調べました。
出てきたのは FLOCSS、ITCSS、BEM、SMACSS、Tailwind 風のユーティリティ系…。正直、多すぎて最初は意味不明でした。

とりあえず読んでみた感じ、僕が目指したいのは「保守しやすい、意味が伝わる、チームでも使える」みたいな方向だったので、
FLOCSS をベースに、クラス名は BEM で書いてみることにしました。

決め手は「ファイルの分け方がわかりやすくて、1人でも回しやすい」「クラス名のルールが明快」ってところ。
とはいえ、いまだに Modifier はどうするか迷ったり、Utility はどこまで許すか悩んでます。
ここは正解を探すより「一旦決めて動かしてみる」しかないかなと思ってます。

フォルダ構成はこうした(けどまだ迷ってる)

FLOCSS に沿って、とりあえずこんな感じにしました

scss/
├ component/
│   _c-button.scss
│   _c-form.scss
│   _c-modal.scss
│   _c-page-intro.scss
│   _c-page-pager.scss
│   _c-table.scss
│   _index.scss
├ foundation/
│   _base.scss
│   _color-map.scss
│   _font-map.scss
│   _function.scss
│   _index.scss
│   _mq.scss
│   _radius-map.scss
│   _shadow-map.scss
│   _space-map.scss
│   _mixin.scss
│   _variables.scss
│   _z-map.scss
├ layout/
│   _l-container.scss
│   _l-footer.scss
│   _l-grid.scss
│   _l-header.scss
│   _index.scss
├ mixin/
│   _border.scss
│   _color.scss
│   _font-size.scss
│   _index.scss
│   _mixin.scss
│   _radius.scss
│   _shadow.scss
│   _spacing.scss
│   _z.scss
├ project/
│   _index.scss
│   _p-about.scss
│   _p-contact.scss
│   _p-home.scss
├ utility/
│   _index.scss
│   _u-animation.scss
│   _u-br.scss

foundation に色とかフォントとかベースを詰め込んで、
layout は全体の枠組み、
component に サイト全体で使うUI パーツ、
project にページ固有っぽいやつ、
utility は応急処置的なやつ。

まだ「utility多すぎるとダメだよな…」とか「projectに何入れるべき?」とか悩んでるので、運用しながら調整中です。

基盤ルールにしてみたこと

rem()って何?から始まって

CSS 設計の記事で「pxはやめてremで書け」ってよく書いてあったので、とりあえずhtml { font-size: 62.5%; }にして、1rem = 10px にしました。
これだけでも数字の計算がだいぶ楽。

さらに「毎回remって書くのめんどくさいな…」と思って、mixin じゃなくて関数で rem(16) みたいに書けるようにしました。

@use 'sass:math';

@function rem($px) {
  @return math.div($px, 16) * 1rem;
}

// 使用例
.button {
  font-size: rem(14);
  padding: rem(8) rem(12);
}

なんで関数にしたかというと、計算がブレずに済むし、16px基準を変えたくなったときに1ヶ所で直せるから。
今のところ不便はないです。

mapって便利そうだなと気づいた

色やフォントサイズをまとめるときに、変数だけだと足りないなと思って調べたら、map っていう手があった。

最初は見た目がごちゃっとしてて敬遠してたけど、慣れると便利すぎる。

特にフォントや余白みたいに、「サイズだけじゃなく行間や字間もまとめたい」みたいなときにすごくいい。

作ったマップたち

まずはフォントマップ。これでレスポンシブにも対応させるので、とても便利です。たとえばxsを指定すると、xsの定義の中でそれぞれのブラウザ幅に対応したサイズに変化してくれます。つまり、実際の値はここのファイルを見るだけでいいんですね。すごく楽!

@use './function' as *;

$font-map: (

  // 補足・キャプション・注釈用
  // 例:フッター注釈、フォームの補足、利用規約など
  'xs': (
    'sm': (size: rem(8), line-height: rem(16), letter-spacing: 0.1em),
    'md': (size: rem(10), line-height: rem(16), letter-spacing: 0.1em),
    'lg': (size: rem(14), line-height: rem(16), letter-spacing: 0.1em)
  ),

  // 小さめの補助情報用
  // 例:カード内の補足テキスト、ボタン補助文など
  'sm': (
    'sm': (size: rem(12), line-height: rem(20), letter-spacing: 0.1em),
    'md': (size: rem(14), line-height: rem(20), letter-spacing: 0.1em),
    'lg': (size: rem(14), line-height: rem(18), letter-spacing: 0.1em)
  ),

  // 通常の本文用(基本テキスト)
  // 例:記事、サービス紹介文、段落テキストなど
  'base': (
    'sm': (size: rem(12), line-height: rem(26), letter-spacing: 0.01em),
    'md': (size: rem(14), line-height: rem(26), letter-spacing: 0.01em),
    'lg': (size: rem(16), line-height: rem(32), letter-spacing: 0.01em)
  ),

  // リード文・概要テキスト用
  // 例:カードタイトル、冒頭リード文など
  'md': (
    'sm': (size: rem(14), line-height: rem(24), letter-spacing: 0.01em),
    'md': (size: rem(16), line-height: rem(28.8), letter-spacing: 0.01em),
    'lg': (size: rem(20), line-height: rem(28.8), letter-spacing: 0.01em)
  ),

  // 小見出し・サブセクション用
  // 例:h3相当の小見出し、セクションタイトルなど
  'lg': (
    'sm': (size: rem(16), line-height: rem(24), letter-spacing: 0.01em),
    'md': (size: rem(20), line-height: rem(30), letter-spacing: 0.01em),
    'lg': (size: rem(24), line-height: rem(36), letter-spacing: 0.01em)
  ),

  // セクション見出し・h2相当
  // 例:メインセクションの見出し、構造見出しなど
  'xl': (
    'sm': (size: rem(20), line-height: rem(30), letter-spacing: 0.02em),
    'md': (size: rem(24), line-height: rem(36), letter-spacing: 0.02em),
    'lg': (size: rem(30), line-height: rem(40), letter-spacing: 0.02em)
  ),

  // HERO直前の強調用(中見出し)
  // 例:ページ冒頭のサブキャッチなど
  '2xl': (
    'sm': (size: rem(24), line-height: rem(36), letter-spacing: 0.03em),
    'md': (size: rem(30), line-height: rem(40), letter-spacing: 0.04em),
    'lg': (size: rem(36), line-height: rem(44), letter-spacing: 0.05em)
  ),

  // HEROキャッチ用(短めコピー)
  // 例:印象的な1文、サービス訴求コピーなど
  '3xl': (
    'sm': (size: rem(30), line-height: rem(40), letter-spacing: 0.03em),
    'md': (size: rem(36), line-height: rem(44), letter-spacing: 0.03em),
    'lg': (size: rem(48), line-height: rem(56), letter-spacing: 0.03em)
  ),

  // HEROメイン見出し(h1相当)
  // 例:大きなビジュアルに重ねるコピーなど
  '4xl': (
    'sm': (size: rem(36), line-height: rem(44), letter-spacing: 0.04em),
    'md': (size: rem(48), line-height: rem(56), letter-spacing: 0.04em),
    'lg': (size: rem(60), line-height: rem(64), letter-spacing: 0.04em)
  ),

  // HEROで数字・単語を目立たせる特大サイズ
  // 例:「2025」や「No.1」などの強調表示
  '5xl': (
    'sm': (size: rem(48), line-height: rem(56), letter-spacing: 0.05em),
    'md': (size: rem(60), line-height: rem(64), letter-spacing: 0.05em),
    'lg': (size: rem(72), line-height: rem(80), letter-spacing: 0.05em)
  ),

そして上記のフォントマップを使うためのmixinです。

@use 'sass:string';
@use 'sass:map';
@use '../foundation' as *;

@mixin font-size($token, $with-line-height: false) {
  $resolved-key: string.unquote($token);
  $font: map.get($font-map, $resolved-key);

  @if $font == null {
    @error "font-mapに '#{$resolved-key}' が見つかりません";
  }

  $sm: map.get($font, sm);
  $md: map.get($font, md);
  $lg: map.get($font, lg);

  // sm(初期サイズ)
  & {
    font-size: map.get($sm, size);
    letter-spacing: map.get($sm, letter-spacing);

    @if $with-line-height {
      line-height: map.get($sm, line-height);
    }
  }

  @include mq(md) {
    & {
      font-size: map.get($md, size);
      letter-spacing: map.get($md, letter-spacing);

      @if $with-line-height {
        line-height: map.get($md, line-height);
      }
    }
  }

  @include mq(lg) {
    & {
      font-size: map.get($lg, size);
      letter-spacing: map.get($lg, letter-spacing);

      @if $with-line-height {
        line-height: map.get($lg, line-height);
      }
    }
  }
}

実際にはこんな感じで使います。

@include font-size('xs');

簡単ですね!

注意点

ここで注意点なのが、@includeは同一ネストの中で一番最後に持ってくる必要があるということです。
例えばこれはNG。

.test {
  @include font-size('xs');
  width: 100%;
}

こっちはOK。

.test {
  width: 100%;
  @include font-size('xs');
}

指定が上書きされる恐れがあるので、必ず@includeは最後に書いてね!ってことらしいです。逆にしてもコンパイルは通るのですが、警告がでますので、精神衛生上よくないですね。 

というわけで、ひとまずここまでが、僕が調べて試したSCSS設計のメモです。
まだまだ途中で、書きたいことは他にもたくさんあります。たとえば、color-mapspace-map、メディアクエリの設計やmixinの話もあるので、それも後でまとめる予定です。

その他に「運用してみてどうだったか」とか「やりすぎて失敗した話」なんかも書いてみようと思っているので、また続きをメモします!

今回は以上です!