Madogiwa Blog

主に技術系の学習メモに使っていきます。

個人的なCSS構成の考え方のメモ📝

世間ではVue.jsのSFC、Tailwind、CSS in JSのような技術を用いてコンポーネント内でカプセル化して統制をとっていくことが主流だと思いますが、なぜか最近ピュアなCSSを書くことが多いので自分なりに設計として考えていることを整理して共有しやすいようにメモしておく。

基本思想

Sassは採用せずにPostCSS + CSSでFLOCSSベースのディレクトリ構成にしつつBEM的な命名規則を採用してCSSを書く。(ただ厳密なプレフィクスとかをレビューでコメントし合うのは生産的ではない気がしているので、そのあたりはゆるふわで運用する感じ)

FLOCSS(フロックス) は、OOCSSやSMACSS、BEM、SuitCSSのコンセプトを取り入れた、モジュラーなアプローチのためのCSS構成案です。 https://github.com/hiloki/flocss

BEM — is a methodology that helps you to create reusable components and code sharing in front‑end development https://getbem.com/

具体的には以下のようなディレクトリ構成を採用し、block__element--modifierの形式で記述する。

またグローバルで利用するスタイルと各ページで利用するスタイルではエントリを分けてビルドし、各ページで利用するスタイルに関してはそれぞれのページで読み込むようにする。

.
├── src/
│   ├── entrypoints/
│   │   ├── application.ts
│   │   └── books/
│   │       ├── index.ts
│   │       └── show.ts
│   └── stylesheet/
│       ├── application.css
│       ├── foundation/
│       │   ├── reset.css
│       │   ├── base.css
│       │   ├── font.css
│       │   ├── variable.css
│       │   └── color.css
│       ├── layout/
│       │   ├── header.css
│       │   └── footer.css
│       └── object/
│           ├── component/
│           │   └── button.css
│           ├── project/
│           │   └── books/
│           │       ├── index.css
│           │       ├── show.css
│           │       └── card.css
│           └── util/
│               └── align.css
└── public/
    └── dist/
        └── stylesheets/
            ├── application.css
            └── books/
                ├── index.css
                └── show.css

ディレクトリの役割と基本ルール

Foundation

FLOCSSと同じ、デフォルトスタイルや変数管理等を行うディレクトリ。

Foundation Reset.cssやNormalize.cssなどを用いたブラウザのデフォルトスタイルの初期化や、プロジェクトにおける基本的なスタイルを定義します。 ページの下地としての全体の背景や、基本的なタイポグラフィなどが該当します。 https://github.com/hiloki/flocss?tab=readme-ov-file#foundation

Foundationで定義したスタイルはapplication.cssにインポートされグローバルに適用する。

/* application.css */

@import "./reset.css"
@import "./font.css"
@import "./color.css"
@import "./variable.css"
@import "./base.css"

Layout

基本はFLOCSSと同じ、ヘッダーやフッター、コンテンツエリアのような具体の要素を埋め込むテンプレート的な要素のスタイルを定義するディレクトリ。

ページを構成するヘッダーやメインのコンテンツエリア、サイドバーやフッターといったプロジェクト共通のコンテナーブロックのスタイルを定義します。 https://github.com/hiloki/flocss?tab=readme-ov-file#layout

あくまでグローバルに現れるテンプレート的な要素となるため、具体なコンテンツを持つ要素や特定の機能のためものは後述のObjectに定義する。

FLOCSSではプレフィクスとしてl-をつけるルールになっているがLayout - Object間の移動を想定して、そこまで厳密にプレフィクスはつけずサービス固有のプレフィクスservicename-を付ける程度にする。

.service-header { ... }
.service-footer { ... }

Object

基本はFLOCSSと同じ、具体なコンテンツを持つ要素や特定の機能のためのスタイルを定義するディレクトリ。

Object OOCSSのコンセプトを元に、プロジェクトにおける繰り返されるビジュアルパターンをすべてObjectと定義します。 https://github.com/hiloki/flocss?tab=readme-ov-file#object

FLOCSSと同様に以下の3つのディレクトリを配下に持つ。

  • Component
  • Project
  • Util

Component

基本はFLOCSSと同じ、グローバルに再利用される要素を管理するディレクトリ。必要な箇所で@importで読み込み利用します。

Component 再利用できるパターンとして、小さな単位のモジュールを定義します。 一般的によく使われるパターンであり、例えばBootstrapのComponentカテゴリなどに見られるbuttonなどが該当します。 https://github.com/hiloki/flocss?tab=readme-ov-file#1-component

Componentに配置するかどうかの目安として異なる2つ以上の機能で3回以上再利用が発生した、またはデザインシステム等で明確にグローバルな要素として定義されている場合にはComponentに配置する。

FLOCSSではプレフィクスとしてc-をつけるルールになっているがLayout - Object間の移動を想定して、そこまで厳密にプレフィクスはつけずサービス固有のプレフィクスservicename-を付ける程度にする。

.service-button { ... }

特定な機能内で再利用されるものはComponentではなく後述のProjectに定義する。

Project

基本はFLOCSSと同じ、特定の機能で利用することを想定した要素に対するスタイルを管理するディレクトリ。

Project プロジェクト固有のパターンであり、いくつかのComponentと、それに該当しない要素によって構成されるものを定義します。 https://github.com/hiloki/flocss?tab=readme-ov-file#2-project

基本的には、まず愚直にページ単位でCSSを作成しページ間で再利用が発生するものは別ファイルに切り出していく。

目安として3回以上の再利用が発生した場合に別ファイルに切り出すことを検討する。

FLOCSSではプレフィクスとしてp-をつけるルールになっているが、namespace classを採用しページ単位で作成したCSSは一意となるようにサービス名を表すプレフィクス+ディレクトリ構成のようなclassをトップレベルに記述することでページ内でCSSをスコーピングし、それにネストする形でスタイルを記述する。

「namespace class」は、1つViewに対して一意のルートクラスを設定する手法です、愚直ですね。Wordpressで似たような仕組みを見た方もいるかと思います。 Viewがapp/views/users/show.htmlのように配置されていますので、ここから.view-users-showのようなクラス名にすることで、1つのViewに一意なクラス名を付与することが出来ます。 https://qiita.com/hanakla/items/b96cdfabd93a762c3ec0#namespace-class

グローバルに影響しないスコープ内のCSSに関してはBEMライクの記法で記述するぐらいのゆるいルールで管理する。

/* project/books/index.css */
.service-books-index {
  .books-title { ... }
  .books-card { ... }
}
/* project/books/show.css */
.service-books-show {
  .books-card { ... }
}

機能内での再利用のために別ファイルに切り出す場合にはサービス名を表すプレフィクス+機能のトップレベルのディレクトリ構成+コンポーネント名のようなclassをトップレベルに記述し、それにネストする形でスタイルを記述してスコーピングを行う。

/* project/books/card.css */
.service-books-card { ... }

Util

基本はFLOCSSと同じ、グローバルに利用する便利系クラスを配置するディレクトリ。必要な箇所で@importで読み込み利用します。

Utility ComponentとProjectレイヤーのObjectのモディファイアで解決することが難しい・適切では無い、わずかなスタイルの調整のための便利クラスなどを定義します。 https://github.com/hiloki/flocss?tab=readme-ov-file#3-utility

FLOCSSではプレフィクスとしてu-をつけるルールになっているがUtility - Project等の移動を想定して、そこまで厳密にプレフィクスはつけずサービス固有のプレフィクスservicename-を付ける程度にする。

基本的にはCSSの単一のプロパティを操作するようなcssとなるため、操作するプロパティや意図に合わせて命名する。

.service-text-center { ... }
.sercice-is-hidden-mobile { ... }

命名規則・クラス設計

厳密性は求めないがFLOCSSと同様にBEM・MindBEMding的なblock__element--modifierの形式で書く。

BEMシステムのシンタックスである、Block、Element、Modifierに分類して構成される規則を採用します。 FLOCSSでは、オリジナルのBEMのシンタックスではなく、MindBEMding のアイデアを基本的にそのまま取り入れています。 https://github.com/hiloki/flocss?tab=readme-ov-file#mindbemding

BEM — is a methodology that helps you to create reusable components and code sharing in front‑end development https://getbem.com/

Blockは単体で意味のある要素のため、Block同士はネストさせない。

Block Encapsulates a standalone entity that is meaningful on its own. https://getbem.com/naming/#block

Element, ModifierはBlockを構成する要素のためBlockにネストするように記述する。

Element Parts of a block and have no standalone meaning. Any element is semantically tied to its block. https://getbem.com/naming/#element

Modifier Flags on blocks or elements. Use them to change appearance, behavior or state. https://getbem.com/naming/#modifier

例えばヘッダー(Element)・メイン(Element)・フッター(Element)を持つカード(Block)的な要素をCSSで表現する場合は以下のような書き方になる。

.service-card {
  .service-card__header { ... }
  .service-card__main { ... }
  .service-card__footer { ... }
  &.service-card--dark { ... }
}

エントリー設計

⚠️ MPAやダイナミックインポートを利用する場合には本章で記載した思想でエントリーを分離する設計を行う。

グローバルに適用するスタイルはentrypoints/application.tsにimportしグローバルに読み込まれるエントリーとして出力します。

// entrypoints/application.ts
import '@/stylesheet/application.css'

Projectで定義した各ページで適用するスタイルは他のページに影響を与えないようにentrypoints/books/index.tsのような各ページのエントリーのtsファイルでimportし各ページで読み込みます。

// entrypoints/books/index.ts
import '@/stylesheet/object/project/books/index.css'

以上のエントリー設計を行い以下のようにグローバルに適用するものと個別のページで利用するものを分けて出力して読み込みます。

└── public/
    └── dist/
        └── stylesheets/
            ├── application.css
            └── books/
                ├── index.css
                └── show.css

参考