フロントエンジニアの茨木です。
皆様はCSSを書く際にコーディング規約を意識しているでしょうか。かつて、弊社にはCSSのコーディング規約が存在せず、CSSファイルの肥大化・クラス命名規則の不統一が発生していました。メンテナンスが難しくなってきた為、1年半ほど前にCSSコーディング規約を設けました。若干のルール追加を伴いながら、現在まで問題なく運用できています。本記事ではフロントエンドで運用しているCSSのコーディング規約に関して紹介します。
導入環境
本記事では以下の環境を前提にしています。
- Ruby on Rails 5.0
- Sass 3.4
- Slim 3.0
CSSコーディング規約のコンセプト
初めにチームでヒアリングを行い、以下のようなコンセプトを決定しました。
- 読みやすい、書きやすいクラス名やタグ構造にする
- スタイルを再利用できる
- セレクタによるスタイルの競合を少なくする
既存の設計手法であるBEM*1やSMACSS*2などの検討を初めに行いました。検討の結果、最もコンセプトに近かったSMACSSを基に、チームの実情に即したルールを独自で設けることにしました。
CSSコーディング規約の概要
上記の3つのコンセプトに基づき、それぞれに対応する以下のルールを設けました。
- ルール1: 読みやすく書きやすいクラス名やタグ構造にするためのルール
- ルール2: スタイルを再利用するための構成要素のルール
- ルール3: スタイル競合を少なくするためのルール
構成要素のルールはSMACSSのそれにかなり近いですが、他のルールは独自で定義したルールです。 これから、それぞれのルールについて紹介していきます。
ルール1: 読みやすく書きやすいクラス名やタグ構造にするためのルール
読みやすい、書きやすいクラス名やタグ構造にするためのルールです。タグ構造自体はHTMLの話ですが、CSSのコーディングに直接関わる部分なので規約に含めています。クラス名やタグ構造に関しては3つのルールを設けています。
ネスト構造をクラス名に含めない & クラス名はハイフン区切り
読みやすさ・書きやすさのためにネスト構造をクラス名に含めないことにしました。2単語以上のクラス名の場合は、CSSの言語仕様に則ってハイフン区切りで単語を区切ります。
class.html.slim
// 良い例 section.popular-users ul li.user span.name hogehoge span.age 25 // 悪い例 section.popular-users ul.users // 以下3行はネスト構造が含まれている li.users-user span.users-user-name hogehoge span.users-user-age 25
タグやクラス名をセマンティックに
タグやクラス名をセマンティックにするために、以下の3つのルールを設けています。
文章構造に適したタグを可能な限り使う
HTML5で定義されたheader、footer、navといったセマンティックなタグを積極的に使うようにしています。クラス名は、必要最小限・セマンティックに命名する
クラス名は、タグで表現しきれない意味を表すために必要最小限だけ振るようにしています。特にタグとクラス名の意味が重複しないように注意しています。divやspanを使う場合は、必ずクラス名を振る
divやspanはタグ自体が意味を持たないので、必ずクラス名を振って意味を与えるようにしています。デザイン上必要なdivには.wrapper
など目的が分かるクラス名を与えています。
correct_tag.html.slim
// 良い例 section.popular-users // デザイン目的のタグであることをクラス名で明示している .wrapper ul li.user span.name hogehoge span.age 25 // 悪い例 .popular-users // divには必ずクラスを振らなければならない div // タグとクラス名の意味が重複している ul.list li.user // spanには必ずクラスを振らなければならない span hogehoge // 文章構造が分からないクラス名 span.small-number 25
文書構造上意味のないタグを可能な限り削減する
擬似要素などを積極的に使い、文書構造上意味のないタグを可能な限り削減しています。 HTML自体がかなり読みやすいものとなるのは勿論、タグが減少することによりスタイル競合のリスクが減少します。 擬似要素が役に立つ例として、以下の画像のような矢印付きリンクボタンを紹介します。
矢印をimgタグで定義することは勿論可能です。しかし、矢印は文書として意味を持たないので、本来はHTML文書内に無い方がセマンティックです。このような場合に、タグ内にCSSで要素を追加できる擬似要素が有用です。以下にコードを示します。
link_with_arrow.html.slim
ul.links li a href='/page1' page1 li a href='/page2' page2
link_with_arrow.sass
ul.links li list-style: none border-top: solid 1px #999 width: 200px &:last-child border-bottom: solid 1px #999 a display: block height: 50px line-height: 50px position:relative text-decoration: none // 擬似要素:afterにより右矢印のスタイルを定義 &:after content: '' display: block position: absolute top: 0 right: 10px width: 14px height: 50px background: url(//cdn.hoge.fuga/images/right_arrow.png) no-repeat center background-size: 6px 12px
ルール2: スタイルを再利用するための構成要素のルール
スタイルの再利用性を高めるために、ベース・モジュール・レイアウト・ユーティリティ・ステートという5つの構成要素を定義しています。ベース・モジュール・レイアウト・ステートに関しては、SMACSSと基本の考え方は同一です。ユーティリティはモジュールと類似していますが、スタンドアローンでない点がモジュールと異なります。ユーティリティは規約策定当初モジュールに含まれていました。運用していく中でモジュールの書き方が多様になってきたので、ユーティリティを別に定義しました。
ベース
各ページ共通のスタイル、各タグの基本となるスタイルを定義します。
_base.sass
header width: 100% h1 font-size: 30px input border: solid 1px #ccc
モジュール
画面上で再利用されるひとまとまりの要素に適用されるスタイルです。スタンドアローンで成り立ちます。
トップレベルに.m-モジュール名
のクラスを必ず持つことを規定しています。mixinにする場合は、後述のユーティリティとの区別のため、m-
から始まる名称にします。
_mixins.sass
@mixin m-items ul.m-items li.item img width: 100% .name font-size: 20px . . .
レイアウト
画面上の大枠のレイアウトを指定するための要素に適用されるスタイルです。l-
で始まるクラス名で定義します。
_base.sass
.l-sub float: left width: 100px .l-main margin-left: 120px
ユーティリティ
頻繁に使用されるスタイルの集合です。スタンドアローンでない点がモジュールと異なります。スタンドアローンでないことを明確にするために、必ず@mixin
で定義すること、一切セレクタを持たないことを規定しています。この構成要素は当初の規約にはありませんでしたが、運用過程で暗黙に使われていました。次第にモジュールと混同されるようになってきたので、ルール化しました。
_mixins.sass
@mixin circle($diameter) width: $diameter height: $diameter border-radius: $diameter/2
util.sass
@import 'mixins' .rank-badge @include circle(20px) background-color: #ccc
ステート
ステートは状態に応じてスタイルを上書きするためのクラスです。適用元のクラスを存置したままマルチクラスで定義します。ステートのクラス名は、適用元要素のクラス名を原則含みません。
state.sass
.m-like-button width: 100% // ステート &.active background-color: #ccc border: solid 1px #999
state.html.slim
ul.users li.user .m-like-button.active いいね済み li.user .m-like-button いいねする
ルール3: スタイル競合を少なくするためのルール
1ビュー1CSSのファイル構成
Railsではデフォルトで全CSSがapplication.cssにまとめられますが、スタイル競合のリスクが高まります。そのため、本規約ではビューごとにCSSの定義ファイルを別にしています。各ページ共通で用いるスタイルは別ファイルで定義し、@import
で読み込むようにしています。
/app/assets/stylesheets/ │ ├ shared/ │ │ │ ├ _base.sass │ ├ _mixins.sass │ └ _reset.sass │ ├ users/ │ │ │ ├ show.sass │ ├ create.sass . . . . . .
show.sass
@import 'reset' @import 'base' @import 'mixins'
ルートでのクラス名/要素名単独のセレクタは禁止
ルートのセレクタはかなり影響範囲が大きいです。ルートでクラス名や要素名を単体で指定した場合には予期せぬ要素にスタイルを定義してしまう危険が高くなります。そのため、ルートでのクラス名/要素名単独のセレクタを禁止しています。このルールは、ベース・モジュール・レイアウト・ユーティリティには適用しません。このルールは当初暗黙のルールでしたが、今後メンバーが加わることを想定してルール化しました。
root_selector.sass
// 良い例 section.popular-articles h2 font-size: 30px // 悪い例1 .popular-articles h2 font-size: 30px // 悪い例2 section h2 font-size: 30px
まとめ
規約により、読みやすさ・書きやすさ・再利用性が向上し、メンテナンスがとても楽になりました。BEMのような厳格な命名規則ではありませんが、セレクタやファイル構成の規約のおかげでスタイルの競合はほとんど発生しません。途中、若干のルール追加を伴いながら1年半運用できており、当初の目的は達成されたといえます。成功の要因としては、安全性と読みやすさ/書きやすさの双方を考慮した点、コードレビューを徹底している点が挙げられます。
読者の皆様も、CSSの実装中にスタイルの競合や再利用性の問題に遭遇することはよくあると思います。そのような場合、CSSの書き方だけでなくファイル構成やタグ構造も見直すことが解決につながるかもしれません。また、規約を継続運用するためには、安全性だけでなく読みやすい・書きやすいという点も重要かもしれません。
最後に
VASILYではWebの力で世の中を変えたいエンジニアを募集しています。 興味がある方は是非こちらからご応募ください。
*1:Methodology / BEM https://en.bem.info/methodology/
*2:Scalable and Modular Architecture for CSS https://smacss.com/