イノグリのCSS設計手法を整理

CSS設計手法っていっぱいあります。OOCSS,BEM,SMACCS,FLOCCSなどなど。

どれも一長一短です。
うちでは、コーディングをずーっとやってきた現場経験をもとに、2021年現在、BEMとSMACCSを融合させたような形を採用していて、それをあらためてここで整理しておきたいと思います。

まずは元になっているBEMとSMACCSについて、基本の思想を理解しておきます。

BEMとは

「BEMとは」で検索して2〜3記事読むとなんとなくわかると思います。

一番詳しいCSS設計規則BEMのマニュアル - Qiita
一番詳しい(当社比)この記事は大体1年くらいBEMを実践した中で溜まった知見的なものをルール・規則・注意点をまとめたマニュアルというかたちにしたもの.BEM初心者でもすぐ実践してもらえそうな感…

Block,Element,Modifierの3つの名前を組み合わせてclass名をつくっていくことでうまいことやろうという考え方です。

イノグリでもこの考え方でやってます(主にBlockとElementを使ってモジュール設計していく)

イノグリ設計と違うところ

BEMではルールに則ってハイフンやアンダースコアなどをつけていきますが、イノグリ設計ではハイフンしか使いません。

.category-list__category-title{}

↑BEMの命名(Elementはハイフンでつないでモディファイアはアンダーバー2つ使うなどのルール)

.category-list-body-title{}

↑イノグリ設計の命名(ハイフンでつないでおりていくだけ)

理由

細かい規定によってハイフンやアンダーバーでつなげていくのだが、その規定の適用場面でコーダーの個別判断が入り、厳密な規定の適用が困難になります。(ここelementなのかmodifierなのかわからんけどとりあえずアンダーバーにしとこ、的な)

そうなると規定がありながら規定からはずれてしまったコードが混在することになりめちゃくちゃになります。

さらに、コーディングして納品した後のソースはお客さんが運用を引き継ぎます。お客さんは通常コーダーよりも規定に精通していないため、アンダーバーとハイフンの混ざった命名に混乱します。

納品後の運用を優しいものにするために、ハイフンに統一したほうがよいというのがイノグリ設計の考え方です。

SMACCSとは

より良いCSSを書くための様々なCSS設計まとめ
CSSは誰でも簡単に自由に書けるのですが、好きなように書いていると「ここを変更したら、違うところが崩れた」といったようにすぐに破綻してしまいます。 さらに、複数…

特徴としては、ベース、レイアウト、モジュール、ステート(状態)、テーマ(外観)の5つにわけてそれぞれcssをつくり、組み合わせて使う(マルチクラスという)という考え方です。

<a class="btn is-primary">ボタン</a>

こんな感じで。

イノグリ設計でもまずベースcss(タグそのものの基本設定)をしたうえで、レイアウトとモジュール、ステートのcssをつくって組み合わせています。

イノグリ設計と違うところ

SMACCSではレイアウト系のcssにl-という接頭語をつけます。

<header class="l-header">
    <div class="l-header-inner">
        <a class="header-logo">イノセントグリーン</a>
    </div>
</header>

↑SMACCSのルールだとレイアウトをつくる要素には「l-」をつける。

<header>
    <div class="header-inner">
        <a class="header-logo">イノセントグリーン</a>
    </div>
</header>

↑イノグリ設計だとl-という接頭語はつかわない。また、ページに1つしか使われないようなタグにはclassをつけないことも可としている。

また、SMACCSではステート(状態)とテーマ(外観)をわけて考えていますが、イノグリ設計ではステートもテーマも同じ扱いで使っています。(テーマについては僕もあんまりよくわかってないところある)

違う理由

これもBEMのところで書いたのと同様なのですが、実際の案件現場では、レイアウトとモジュールを厳密にわけるのは難しい場面が多いです。

厳密に考えすぎるあまり、<div class=”l-box box”>のような命名が乱立しがちです。
(l-boxにはfloat:leftだけついていて、boxにborderやcolorが指定されている)

マルチクラスが分割しすぎてしまってもcss本来の流用性が損なわれますし、scss等でもl-の接頭語がついていたりすることで同じ指定をしている2つのcssが遠いところに書かれているということもよくおこります。

レイアウトとモジュールは融合してくるものなので、無理にわけようとせずに、大きなブロックわけだけ「レイアウト」として考えて、コンテンツ内ででてくるブロック要素(BEMでいうところのブロックレベルのもの)はすべてモジュールとして作ってしまうというのがイノグリ設計です。

大きなブロックわけとは、ここでは以下のようなclassです。

.container{}
.wrapper{}
.main{}
.side{}
.section{}
.inner{}
header{}
footer{}
article{}

これらの大きなブロック分けでのみレイアウトという考え方をするのであればわざわざここだけl-接頭語を使うのもややこしいので、l-接頭語自体を使わないルールにしています。

BEM同様クライアントに引き渡した後にも「あれ、l-ってなんなんだっけ」という余計な混乱を引き起こさずにすみます。(containerだけで十分、あぁ一番外枠はcontainerという名付けなのね、と伝わるのでレイアウトですよーという宣言であるl-を付ける必要ない)

以上のようなBEMとSMACCSの汎用性が高い部分を組み合わせたものがイノグリ設計になります。
ネットで多く反乱してるような○○○CSSみたいな名前はつけてません。

イノグリ設計

基本ルール

  • 使うcssはリセットcss、ベースcss、レイアウトcss、モジュールcssである。
  • リセットcssはいつものを流用
  • ベースcssは案件によって個別に指定(pタグの最初のfont-sizeやaタグの初期の色やtext-decorationなど)
  • レイアウトは前章で書いたような決まったブロック名を使い、その中にはBEMの考え方で名前をつけていく
  • モジュールはBEMの考え方で名前をつけていく
  • BEMの考え方で名前をつけていくがすべてハイフンを使ってつなげていく
  • Block-Element-Elementのように複数のElementを使っていってよい(子要素にいくにつれてつなげて掘り下げていってよい)
  • ただし、Element数が最小になるようにする。(後述)
  • Modifierは使わずにSMACCSのステートとしてis-○○を組み合わせて使う。(後述)
  • そのモジュールのコアとなる部分の一番親にclass=”block”を名付けるがデザイン上の必要に応じてその外側にclass=”block-wrapper”を1つだけ使ってもよい。

BEMだがElement数が最小になるようにする、とは

例えばスクエアエニックスさんのサイドバーにあるこういうデザインをつくるときをケースに考えてみます。

これのblock名をsideitemだとして、Elementをどんどん子供へ子供へとつなげていくとこのようになってしまう。

<div class="sideitem">
	<ul class="sideitem-list">
		<li class="sideitem-list-item">
			<a href="">
				<div class="sideitem-list-item-image">
					<img src="" alt="">
				</div>
				<div class="sideitem-list-item-detail">
					<h3 class="sideitem-list-item-detail-title">ファイナルファンタジーぬいぐるみ</h3>
					<p class="sideitem-list-item-detail-price">3,080円(税込)</p>
					<p class="sideitem-list-item-detail-date">発売技:2021.02.05</p>
					<p class="sideitem-list-item-detail-category">グッズ</p>
				</div>
			</a>
		</li>
		<li>
			2つめのアイテム
		</li>
		<li>
			3つめのアイテム
		</li>
	</ul>
</div>

これでも堅牢な作りではあるけれど、ひたすら子供へいくにつれてハイフンが増えていって冗長になってしまうので、削れるところは削る(Block名-Element名の形になっていれば親Elementを受け継いでいく必要はない)

がっつり削ると以下のようになる(あくまで一例)

<div class="sideitem">
	<ul class="sideitem-list">
		<li>
			<a href="">
				<div class="sideitem-list-item">
					<div class="sideitem-image">
						<img src="" alt="">
					</div>
					<div class="sideitem-detail">
						<h3 class="sideitem-title">ファイナルファンタジーぬいぐるみ</h3>
						<p class="sideitem-price">3,080円(税込)</p>
						<p class="sideitem-date">発売技:2021.02.05</p>
						<p class="sideitem-category">グッズ</p>
					</div>
				</div>
			</a>
		</li>
		<li>
			2つめのアイテム
		</li>
		<li>
			3つめのアイテム
		</li>
	</ul>
</div>

-list-item-detail-○○というふうに全部つなげなくても、sideitemブロックの中にタイトルになるものが1つしか出てこないのならばsideitem-titleにしてしまってok。
もちろんタイトルになるものが1つじゃないケースも多いのでその場合は
.sideitem-title{}
.sideitem-list-head-title{}
.sideitem-list-detail-title{}
などのように必要に応じてElement名を間にはさみながら長くしていく。

話はそれるけれど、ul > liのliでそれ以上したにliが出現しない(liの下にもういちどliがでてくるようなケースも多いですよね)ことがわかっているような場合はliにclass名を省略しちゃうのも可としています。

もうひとつ補足するとこのレイアウトの場合画像が左で内容が右に出てくるのでその親にdisplay:flexを当てる必要がでてくる。そのときにaタグにflexをあてると、のちのち困ることがある。(ここはリンクじゃなくていいのでリンク外しておいて〜といわれると破綻する設計)
なのでaタグの中に1つdivをはさんでそのdivにdisplay:flexを指定するとリンクでもリンクじゃなくてもデザインがくずれずに安全な設計になる。
(名前はsideitem-list-itemでもsideitem-list-innerでもOK)

無理してElementを減らそうとしすぎるとあとで似ているモジュールが出てきたときに破綻しやすいので、Block1つとElement1つまで絞る!と意気込みすぎない程度に

「あぁ、この途中の-content-とかはさすがになくてもいいな」、と思ったら取り除いていってElement数がすくなるようにする意識でやってみてください。

Modifierは使わずにSMACCSのステートとしてis-○○を組み合わせて使う。

こういうとわかりにくいのでわかりやすく説明します。

BEMのModifierはバージョン違いのものに対してつかってました。
例えば、通常のボタンと注意ボタンがあったときにBEMルールだとこうなる

<a href="" class="btn btn-alert">

これなら短いのでまだ「いっかぁ」という気もする。でもさっきのスクエニの商品の見出しで赤文字のバージョンがでてきたときのModifierを考えるとこうなってしまう。

<h3 class="sideitem-title sideitem-title-red">ファイナルファンタジーぬいぐるみ</h3>

Block-Elementとつないだ最後にModifierで違うバージョンを指定するため、sideitem-titleという長い命名が2つも並んでいることになり、htmlが煩雑になる。

なので、状態違い(ここでいう赤文字)を作るときにはSMACCSのステートのやり方をもとに.is-〇〇という状態名だけのclassをつくってhtmlをすっきりさせる。
↓がイノグリ設計

<h3 class="sideitem-title is-red">ファイナルファンタジーぬいぐるみ</h3>

ここで注意点がある。is-redのように短くしたからといって、どこでも使える汎用classをあてたわけではない(あくまでsideitem-titleのモディファイア/ステートとしてis-redとつけている)ので、cssではis-redを単体で書いてはいけない。

✕ NG  .is-red{font-size:#cc0000}

○ OK  sideitem-title.is-red{font-size:#cc0000}

あくまでis-redはsideitem-titleのモディファイアの役割なので、他で使われているis-redに影響を与えないようにcssではsideitem-title.is-redと限定させた書き方をする。

イノグリの作り方で多用されるis-activeも同様で、is-active単体でcssを書かずにかならずもととなるclassにくっつけた形でcss側は書くようにする。

block-wrapperを使って良いケース

基本的にはそのモジュールの一番親にあたるdivに<div class=”block”>と名前をつけて、その中にclass=”block-element-element”と作っていく。

レイアウトの問題でblock内で余白をもたせたりサイズをもたせたりするときにまず使うのは

.block-inner{}

という-innerの命名。

しかしblockの更に外側を横幅100%のベタ色で囲まれてるデザインのためそのベタ色ようにblockを使うのは適さない場合や、すでに-innerが使われていてさらにもうひとつ外にdivを作る必要がある場合など、コーダーの判断で外側に.block-wrapperを作っても良い。

<div class="news-wrapper">
	<div class="news">
		<div class="news-inner">
			<ul class="news-list">
				<li></li>
			</ul>
		</div>
	</div>
</div>

また、よく出てくるケースとして、タイトルやテーブルの外側をblock-wrapperで囲むのもある

<div class="greeting-title-wrapper">
	<h2 class="greeting-title">見出し</h2>
</div>

greeting-titleという見出しそのものはh2につけたい。でもデザイン上タイトルを囲む形で装飾がしてあったりレイアウトが発生している場合には外側にblock-wrapperを使う。

<div class="historytable-wrapper">
	<table class="historytable">
		<tr>
			<th></th>
			<td></td>
		</tr>
	</table>
</div>

block名がhistorytableと「table」という文字をつかっている以上このblock名はtableタグにつかいたい。しかいtableの外側にdivがどうしても必要(余白をとったりspでscrollさせたりする)なときに外側をhistorytable-wrapperで囲む。

ルールが厳格で申し訳ないですが、以上がblock-wrapperを使って良いケース(不必要な場合はできるだけblock-innerというふうにして子要素にほっていくほうがよい)

cssの設計と命名については大体説明しおえたので、次に2つのレベルのモジュールについて説明します。

最小モジュール(部品)と複合モジュール(コンポーネント)

また専門用語使いだしてすいません。

最小モジュールとはどこかのblock内でもどこかのblockの外でも単体で使えるもの。部品と言ってもいい。

例としては以下のようなもの

<a class="btn">ボタン</a>
<h3 class="title-section">見出し</h3>

複合モジュールというのがいわゆるblock-element構造で使われるモジュール。これがコンポーネントとか言われれてるやつ。

<div class="news">
	<div class="news-inner">
		<ul class="news-list">
			<li>
				ニュースです
			</li>
		</ul>
	</div>
</div>

複合モジュールもblock単位でコピペしてどのページでも使える(cssさえ読み込まれてれば)という思想で設計される。

複合モジュールの中に最小モジュールは組み込める。

<div class="news">
	<div class="news-inner">
		<ul class="news-list">
			<li>
				<h3 class="title-section">ニュースです</h3>
				<a href="" class="btn">詳しく見る</a>
			</li>
		</ul>
	</div>
</div>

なので設計順序としては、

①まず部品化できる最小モジュールをデザインから抜き出す。
②その部品を使いつつ複合モジュールを作っていく

の順になる。

そうしないと最小モジュールとして使いまわしできるはずだったボタンや見出しもいちいち複合モジュール内の要素としてcssを余計に作っていってしまうことになる。

最小モジュールから作り始めれば余計な重複cssを減らしていって見通しもよくなり、cssを書く量もへり、スピードもアップする。