「どうやってコーディングをして組み立てていこうか?」
いくつもの案件を経験しても、いつも悩んでしまうのがCSSの書き方です。「それなら自分なりの考えをまとめてルールを作ってしまおう」と考え、CSS設計に関する情報から自分なりにコーディングルールを作りました。
今回の内容は社内勉強会で発表した「CSSのファイル構成と命名規則」の資料を再編したものです。
すべての案件内容で最適な方法ではないこともあると思いますので、1つの考え方だと捉えていただけるとありがたいです。
詳しいコードやルールはGitHub(個人のアカウント)を参照してください。「使用しているテンプレート」リンク先のstyle.scssで実際の全体の構成が確認できます。
CSSは影響範囲の管理が難しい
CSSで難しいことのひとつは「影響範囲」を管理することだと思います。
- クラスを追加したら名前が重複していて余計なスタイルがついてしまった
- IDセレクタで指定されているので上書きするにはIDセレクタを加えなくてはいけない
- 要素セレクタに装飾的なスタイルがついているので毎回消さないといけない
- style属性が指定されているので
!important
でしか上書きできない
CSSはすべてがグルーバルスコープなので、言語仕様的に重複や上書きが避けられません。詳細度、プロパティの継承、カスケーディングというCSS独自の仕様がさらに難しくさせます。
影響範囲を管理するためのローレベルな方法として「名前で擬似的にスコープをつくる」「モジュール化して適応される順番を管理する」ということをしています。
「ローレベルな」としているのはチームとして制作をする場合に学習コストがなるべく高くならないようにしたいということと、案件によってはSassなどで管理するのが難しい場合もあると思うので、あまり技術に依存しない方法がベターだと考えているからです。
名前で擬似的にスコープをつくる
名前はクラス名、ファイル名、レイヤー名、接頭辞(プレフィックス)という4つの順番で考えていきます。
- クラス名の命名規則(MindBEMding, SMACSS)
- ファイルのモジュール化
- モジュールをレイヤーでまとめる
- クラス名に接頭辞(プレフィックス)をつける
これらの命名規則は「FLOCSS(フロックス)」をベースにしています。また、以降の説明はSass(scss記法)を使う前提で進めていきます。
クラス名の命名規則(MindBEMding, SMACSS)
クラス名の付け方はBEM(ベム)を元にした「MindBEMding(マインドベムディング)」をベースにしています。
BEMは親要素であるBlockに対して、子要素であるElementやバージョン違いであるModifier(モディファイア)が依存する関係性になります。詳細度はなるべくフラットな状態になるように、ElementとModiferはBlockを連結させないようにします。
// Good
.block {} // 親要素
.block__element {} // 子要素
.block--modifier {} // 親要素のバージョン違い
.block__element--modifier {} // 子要素のバージョン違い
// Bad
.block {}
.block .block__element {}
.block .block--modifier {}
.block .block__element--modifier {}
ElementとModifierはBlockの名前を引き継ぐことで、どのBlockを親としているのかという関係性を示しながら、名前の重複を避けています。クラスセレクタにしかスタイルを指定しないのもBEMの特徴の1つです。
HTMLはこのようになります。
<div class="block block--modifier">
<div class="block__element block__element--modifier"></div>
<div class="block__element">
<div class="block__sub-element"></div>
<div class="block__sub-element"></div>
</div>
</div>
実際にはblock__element--modifier
を使うことはほとんどなく、以下のようにblock--modifier
とblock__element
を結合子でつなげて指定をしています。詳細度は上がりますが、HTMLにクラスが増えすぎるのも運用しにくいと思っているからです。
.block--modifier > .block__element {}
JavaScriptで動的に変更するようなスタイルはSMACSS(スマックス)のステートクラスを使います。
// タブコンポーネントの表示切り替え
.tab__trigger.is-active {}
.tab__content.is-active {}
// ナビゲーションコンポーネントのスタイル変更
.global-nav__item.is-current {}
ファイルのモジュール化
クラス名の重複はBlock名が重複しなければ防ぐことができます。どんなBlockがあるのか一覧できるように、Blockごとに1つのscssファイルを作成します。
例えば.grid
には_grid.scss
、.button
には_button.scss
のようにです。
@import "object/component/_grid";
@import "object/component/_button";
メディアクエリやこのBlockでしか使用しない変数なども、1つのファイルとしてまとめておきます。
モジュールをレイヤーでまとめる
モジュール化したファイルはセレクタの種類、詳細度、汎用性、役割などによってレイヤーとしてまとめます。基本的に詳細度が弱いものからはじめて徐々に強くしていくと、無理な上書きがなくなります。
css/
├── foundation/
├── layout/
├── object/
│ ├── component/
│ ├── project/
│ └── utility/
└── style.scss
Foundationレイヤー
FoundationレイヤーはNormalize.cssやリセットCSS、要素セレクタ・属性セレクタのような最低限のスタイルを指定します。詳細度を極力弱く、影響範囲を極力狭くします。
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
a {
text-decoration: underline;
color: #2b70ba;
}
a:hover,
a:active,
a:focus {
text-decoration: underline;
color: #5997d9;
}
img {
max-width: 100%;
height: auto;
vertical-align: middle;
}
Layoutレイヤー
Layoutレイヤーはワイヤーフレームに定義されるような大きな単位のコンテナブロックが該当します。このレイヤー以降はクラスセレクタのみを指定します。例外として、このLayoutレイヤーにだけは必要に応じてIDセレクタを指定することもできます。
.l-header {}
.l-footer {}
Objectレイヤー
Objectレイヤーは「OOCSS(オーオーシーエスエス)」のコンセプトをもとにしたビジュアルパターンが主に該当します。ObjectレイヤーはComponent、Project、Utilityの3つの子レイヤーにわけられます。
Componentレイヤー
Componentレイヤーは多くのプロジェクトで横断的に再利用できるような小さな単位のモジュールが該当します。OOCSSの構造の機能を担い、固有のサイズや装飾的なスタイルを極力含まないようにします。
.c-grid {
display: block;
margin: 0;
padding: 0;
font-size: 0;
list-style-type: none;
}
.c-grid__item {
display: inline-block;
width: 100%;
font-size: medium;
font-size: 1rem;
vertical-align: top;
}
Projectレイヤー
Projectレイヤーはプロジェクト固有のスタイルが該当します。プロジェクトで使い回すスタイルのほとんどはProjectレイヤーに追加することになります。
.p-breadcrumb {
overflow: hidden;
margin: 0;
padding-left: 0;
white-space: nowrap;
text-overflow: ellipsis;
list-style-type: none;
}
.p-breadcrumb__item {
display: inline-block;
&:last-child {
display: inline;
}
}
.p-breadcrumb__link {
display: inline-block;
}
Utilityレイヤー
Utilityレイヤーはいわゆる汎用クラスのことで、ほとんどの場合で単一のスタイルを持っています。確実にスタイルを適応させるために!important
を指定します。Sass側ではレスポンシブにも対応できるように、@each
を使ったMixinを使っています。
.u-db {
display: block !important;
}
@media screen and (min-width: 400px) {
.u-db-sm {
display: block !important;
}
}
@media screen and (min-width: 768px) {
.u-db-md {
display: block !important;
}
}
@media screen and (min-width: 1000px) {
.u-db-lg {
display: block !important;
}
}
@media screen and (min-width: 1200px) {
.u-db-xl {
display: block !important;
}
}
さらに以下のようなレイヤーを追加することもできます。
- Variable - プロジェクトで使用されるグローバル変数
- Function - プロジェクトで使用されるグローバルなfunction
- Mixin - プロジェクトで使用されるグローバルなmixin
- Vendor - Normalize.cssやBootstrapのような外部のライブラリやフレームワーク
- Vendor-extension - Vendorレイヤーの上書き(オーバーライド)
- Theme - SMACSSのThemeモジュールに該当するテーマによる色の切り替えなど
- Scope - ブログの投稿用スタイルなどのスコープを作る
- QA/Test - Quality Assurance(品質保証)、もしくはテストのための一次的なスタイル
CSSプリプロセッサ(SassやStylusなど)を使う場合はVariable、Function、Mixinの3つのレイヤーは必ず追加されます。
レイヤーに関してはFLOCSSの他に以下の2つのドキュメントを参考にしています。英語の記事ですが、とても参考になりました。
レイヤーの追加場所は詳細度の強さによって決まります。例えば以下のような順番になります。
css/
├── foundation/
│ ├── variable/
│ ├── function/
│ ├── mixin/
│ ├── vendor/
│ ├── vendor-extension/
│ └── base/
├── layout
├── object/
│ ├── component/
│ ├── project/
│ ├── theme/
│ ├── scope/
│ ├── utility/
│ ├── test/
└── style.scss
使い回しをしないページ専用のスタイルはレイヤーに含めずに、任意のHTMLファイル専用のsingle.scssとして作成します。single.scssは任意のHTMLでしか読み込まれないというスコープを持つことになります。
クラス名に接頭辞(プレフィックス)をつける
レイヤーにまとめたモジュールは、そのレイヤーごとに接頭辞(プレフィックス)をつけます。
.l-
Layoutレイヤー.c-
Componentレイヤー.p-
Projectレイヤー.u-
Utilityレイヤー.t-
Themeレイアー.s-
Scopeレイヤー.qa-
.te-
QA/Testレイヤー.is-
クリックなどのイベントが発生している要素に付与する.js-
JavaScriptから参照される要素(スタイルは当てない)
個別ページ用のCSSも接頭辞をつけたほうがベターです。接頭辞をつけることで名前の重複をさらに抑えることができます。「Bootstrap」や「Foundation」などのCSSフレームワークを併用する場合は特に有効です。
そして接頭辞によって、
- どういった役割をもっているのか
- CSSファイルのどのあたりに記述されているのか
- どれぐらいの詳細度をもっているのか
といったことが名前だけで判断できるようになります。
まとめ
複数人でコーディングをしたり誰かに教えたりするときに名前の付け方やファイル構成のルールをドキュメントとして残しておくと、正確に伝わり、一貫性を保ったまま案件を続けていくことができると思います。
「名前を決める」というのはCSSでいちばん難しいことだと感じています。だからこそ、命名規則のルールはCSSを書くうえで必要なことだと思います。実際に案件を進めていると「この場合はどうすればいいんだろう?」と思うことは出てきます。そんなときには、まわりに相談をしたり、柔軟に考えていくことも大切だとな感じています。
他の人が見ても、半年後の自分が見ても理解しやすいCSSを書いていきたいですね。
今回紹介しきれなかったことも書いてありますので、参考にしてみてください。
補足
ミーティングで発表をしたあとに、いくつか質問をいただきました。Q:は質問で、A:は今自分ならこうするだろうなという答えです。案件によっても変わってくることもありますので、ひとつの参考になればと思います。
Q:用意しているComponentレイヤーで使わないものが出てきた場合削除しますか?インポートをしないようにしますか?
A:インポートをします。サイズ的にも小さなものですし、実はどこかで使っていたということも起こりえるので残しておいた方がベターだと思います。
Q:モジュール化するとscssファイルが増えますが、その辺りはどう?
A:増えますが問題ないと考えています。逆にモジュール化せずに増えた場合よりも管理はしやすいと思います。使い回さないスタイルは各HTML専用のCSSファイルに記述されますし、いくぶんか分散されると思います。
Q:Modifierは意味のある名前にしますか?連番にしますか?
A:コーディングをする段階でパターンが揃っている場合は意味のある名前にします。パターンが揃っていなかったり、あとで追加をする場合は.block--01
のように連番で管理をします。数が増えるごとに適切な名前をつけることは難しくなると思います。連番の場合はコメントやスタイルガイドで補足するなどが必要だと思います。
Q:たくさんのボタンの種類がありました。どのレイヤーに置けばいい?
A:Projectレイヤーに置いてください。追加するほとんどの使い回すスタイルはProjectレイヤーだと考えてください。
ボタンのベーススタイルはComponentレイヤーに定義してあるのでclass="c-button p-button--primary"
のように必要なスタイルだけ上書きします。アイコンがあった場合はclass="c-button p-button--primary p-icon-name"
のようにクラスのつけかえで対応できるようにします(もしくは.p-button--icon-name
)。
Q:ステートクラスはどのようにスタイルをつけますか?
A:.is-active {}
のようなステートクラス単体にはスタイルをつけません。.block.is-active {}
のように連結させた状態でスタイルをつけます。
Q:Componentレイヤーはどんなものが該当しますか?
A:ほぼ追加されないと思っても大丈夫です。色やボーダーなどの装飾的なスタイルがついている場合やコンテンツの中身に使用するスタイルはProjectレイヤーだと考えてもいいです。大枠のレイアウトで装飾的なスタイルをもっていない使い回せるものはComponentレイヤーに入る可能性があります。