普段使いの CSS 設計（FLOCSS ベース）について整理する

## 概要

CSS 設計にはさまざまなアプローチがありますが、自分は普段 Sass 環境下での [FLOCSS](https://github.com/hiloki/flocss?tab=readme-ov-file) をベースにコーディングを行うことが多いです。
CSS モジュールやカスケードレイヤーなど、ブラウザによる新しい機能のサポートによって設計面を深く気にせずとも簡単に壊れにくい CSS が記述できるようになりつつありますが、根本となる設計手法について知っておく・定めることで、多くの環境で汎用的に使用できる CSS の書き方を行うことができます。
本記事では自分が CSS 設計を行う上で重視している部分と、FLOCSS の設計に加えて管理を行っている部分についてまとめます。

## 設計で重要視している部分

### 共通のスタイルか、それ以外か

**「共通のスタイル」と「ユニークなスタイル」を適切に切り分ける**ことが CSS 設計において最重要だと考えています。
この部分をベースとして、「ではどういったルールを設定してファイル管理や上書きを行うか？」という判断を行ない、設計ルールに落とし込みます。
自分は長く FLOCSS ベースでの設計を愛用していますが、共通のスタイルかどうかの管理が明確であれば、経験上おおむねどの CSS 設計手法であれメンテナンス性は大きく変わらないかと思っています。（慣れや好みの問題はさておき）

CSS の詳細度とカスケーディングは、スタイル適用の順序に大きな影響を与えます。
これらを適切に制御することが CSS 設計の必要性であり、デバッグやメンテナンスを容易にします。

### セレクターの目的を明確に

CSS セレクターはスタイルの適用範囲を限定するために使われますが、命名によってその目的を明確にすることで、コードの可読性とメンテナンス性を担保します。

- **共通スタイルとユニークスタイルの区別**
プロジェクト内で複数のページやコンポーネントにまたがって使用される共通スタイルは、その目的が明示されるべきだと考えています。

- **状態や JS のトリガーの明示**
たとえば JavaScript からアクセスすることを前提としたクラスについては、他と区別するための命名規約を設けるなど、セレクターの役割に応じて命名を適切に管理します。

### 極力環境に依存しない CSS 設計を行う
前述の通り、CSSモジュールの使用やカスケードレイヤーなどで名前空間によってスタイルの衝突や詳細度の管理はより楽になっているかと思います。しかしコードを別環境に移植する必要があるケースやチームでの共同開発を視野に入れると、環境によって書き方が大きく変わってしまうのはよい方法ではないかと思います。
  ベースとなる基本設計自体はどのような開発環境であれ、一貫性を保っている必要があるかと考えます。

## 追加・許容するルール 🟢

### 汎用レイアウトスタイルの配置
共通のレイアウトスタイル（例：`.l-contents`, `.l-section-inner`）は Layout のレイヤーに整理します。FLOCSS 設計の Layout レイヤーではユニークな ID 属性に対してスタイルを適用しますが、要件に応じて柔軟にクラスを使用することも認められています。
別記事で詳細を記載しているため、[FLOCSS 設計における Layout レイヤーの再定義](https://qiita.com/yuzuru_xa/items/1d073eb9101bc5fd829f) を参照してください。

### JavaScript 操作対象セレクター
主に「スタイルが当たっていないセレクターなので不要である」と判断し、HTML 側からクラスが削除された結果、 JavaScript が動かなくなってしまうというケースを防ぐ目的です。
  JS から制御されるセレクターには `js-` プレフィックスをつけ、スタイリング用のクラスと一線を画します。これにより、JavaScript で操作する必要のある要素をすぐに判別でき、スタイルの意図しない上書きを回避します。

### ネイティブ属性の活用

`aria` 属性や HTML ネイティブの状態（例：`:disabled`、`:checked`）を使うスタイリングを推奨します。これにより、アクセシビリティの向上やブラウザが提供するデフォルト機能を活かすことができます。
たとえば非活性ボタンのクラスを `.c-button.is-disabled` などで用意してスタイリングを行うケースがあるかと思いますが、ネイティブの HTMLButtonElement に関しては `.c-button[disabled]` に対してスタイリングを行うことで、ブラウザ側での標準挙動とスタイルの管理を一致させることが可能です。

```scss
// NG
.c-button.is-disabled {
  background-color: #ddd;
  color: #fff;
}
```

```scss
// OK
.c-button[disabled] {
  background-color: #ddd;
  color: #fff;
}
```

## 制限するルール 🚧

### ユーティリティクラスの使用制限

ユーティリティクラスの命名および使用法は [TailwindCSS](https://tailwindcss.com/) を基準とし、`u-` のプレフィックスで管理します。
あくまでも utility であるので、過剰な追加や乱用には注意します。各クラスは基本的に 1 つのスタイリングプロパティに集中させ、スタイルの予期せぬ上書きやメンテナンス性の低減を避けます。
`.sr-only`（スクリーンリーダーのみで読み上げられる不可視要素） など、「スタイルが単体で機能すること」を目的とするものについては例外として許容します。

## 禁止事項 ❌

### SCSS 特有のセレクター結合の禁止

SCSS の`&`を使ったセレクターの結合は可読性を下げ、スタイルの意図を曖昧にするため禁止します。また、不要に詳細度を上げてしまうリスクを排除します。
具体的には下記のようなものです。

```scss
.card {
  &__element {
    padding: 10px;
  }
}
```

おおむね思想としては [2019/07/23 Sassの&を使った結合セレクターを禁止する #6](https://github.com/manabuyasuda/til/issues/6) が近く、下記のような影響を避けるためのルールになります。

- 検索性が悪い
  - 検証ツールからクラス名をコピーして検索しても、分割されていて検索に引っかかりづらい
  - modifier と見間違える、ネスト記述の `.` の有無で必要以上に検索でヒットしてしまう
- ネストのルールが増え、出力される CSS が想像しにくい
- 肥大化したコンポーネントで、& に結合されるセレクターを確認する際にトップまでスクロールする必要がある

ただし検索性や管理に大きく影響のないケースでは許容しているケースもあります。
下記のようなものについてはコンポーネントの名称で検索する、または分割されたファイルで管理することとしています。コンポーネントが肥大化した場合は管理が大変になってしまう印象もありますが、そうなった場合はそもそもコンポーネント自体の設計を考え直すべきであると考えています。

- 擬似クラス、擬似要素、属性との結合

```scss
// OK
.c-button {
  transition: 0.15s opacity linear;

  &:hover {
    transition: 0.2s opacity linear;
    opacity: 0.7;
  }

  &[aria-disabled="true"] {
    background-color: #ccc;
    color: #fff;
  }
}
```

- modifier の結合

```scss
// OK
.c-button {
  &--primary {
    background-color: #4caf50;
    color: #fff;
  }
}
```

### Sass の Extend を禁止する

FLOCSS では「モジュール内で完結する Extend」の使用は許容されていますが、コンパイルされるファイルで必要以上にセレクターの記述が肥大化してしまう上に、マルチクラスで modifier を使用する前提の設計ではあまり必要性がないため使用を避けます。

```scss
// NG
.button {
  display: inline-block;
  padding: 0.5em 1em;
  cursor: pointer;
  border: none;
}

.button--primary {
  @extend .button
  background-color: #00f;
  color: #fff;
}

.button--secondary {
  @extend .button
  background-color: #ff0;
}
```

```scss
// OK（SCSS 特有のセレクター結合の禁止 で前述していますが、modifier を & で結合は OK）
.button {
  display: inline-block;
  padding: 0.5em 1em;
  cursor: pointer;
  border: none;

  &--primary {
    background-color: #00f;
    color: #fff;
  }

  &--secondary {
    background-color: #ff0;
  }
}
```

CSS にコンパイルされた後の出力は下記のようになります。

```css
/* NG (Compiled) */
.button,
.button--primary,
.button--secondary {
  display: inline-block;
  padding: 0.5em 1em;
  cursor: pointer;
  border: none;
}

.button--primary {
  background-color: #00f;
  color: #fff;
}

.button--secondary {
  background-color: #ff0;
}
```

```css
/* OK (Compiled) */
.button {
  display: inline-block;
  padding: 0.5em 1em;
  cursor: pointer;
  border: none;
}

.button--primary {
  background-color: #00f;
  color: #fff;
}

.button--secondary {
  background-color: #ff0;
}
```


### 異なるレイヤーのスタイル上書きをしない

スタイルの上書きは同一レイヤー内で行い、異なるレイヤーの上位指定による多層的なスタイル上書きを防ぎます。
結果として不用意に詳細度を上げる必要が出てきたり、必要以上に `!important` を書くことになったりするのを避けるためです。また、どのスタイルがどこから上書きされているか？を管理する目的でもあります。

```scss
// NG
.button-container {
  .c-button {
    // styles
  }
}

// NG
.button-container {
  > .c-button {
    // styles
  }
}
```

## まとめ

共通スタイルとユニークスタイルの明確な切り離し、詳細度やカスケーディングの適切な設計管理は、CSS の可読性とメンテナンス性を大きく向上させます。
基本は FLOCSS をベースに、迷いがちな箇所に対して明確にルールを設定することで、必要以上に悩まず CSS を組み立てることができます。

日々進化する Web 制作の現場においても、根本となる設計指針は変わらず役割を果たします。
本記事ではあくまで自分の設計手法ですが、CSS 設計を悩んでいるマークアップ・フロントエンドエンジニアにとってこれらの手法が実践的な設計指針として役立つことを願っています。