Manage your CSS
with prefixes.
<header class="ly_header">...</header> <!-- layout -->
<main class="ly_cont">
<div class="bl_jumbotron"> <!-- block module -->
<h2 class="bl_jumbotron_ttl">PRECSS</h2>
<p class="bl_jumbotron_txt hp_mb20">CSS with prefixes.</p> <!-- helper -->
</div>
</main>
<aside class="ly_side">
<a href="#" class="el_btn">Try PRECSS</a> <!-- element module -->
<a href="#" class="el_btn el_btn__blue">Back</a> <!-- modifier -->
</aside>
<footer class="ly_footer">...</footer>
OOCSSやSMACSS、BEMの素晴らしさ巧みに取り入れ
更に進化させた強力なモジュール設計。それがPRECSSです。
全てに接頭辞を付加-
PRECSSの管理下にあるクラスは、全て種類に応じた接頭辞が付加されます。これにより接頭辞を一目見ただけで役割、スコープを把握することができます。
もうモジュールの粒度に悩まないでください。
シンプルな命名規則-
クラス名は全てスネークケースとキャメルケースの混成で構成され、それらの使い分けには明確なルールがあります。
省略語についても指針を示しているため、今までのように悩んだあげく、とても長い名前を付ける必要はもうありません。
親しみやすい設計-
PRECSSは全く新しい設計思想ではありません。OOCSSやSMACSS、BEMなど今までの賞賛すべき素晴らしい思想が基になっています。
どれか1つでも知っていれば、PRECSSは親しみやすいものに感じるでしょう。
他種族との共存-
明確な独自記法により、あなたが書いたクラスとCMSやCSSフレームワークが出力したクラスを明確に区別することができます。
全てPRECSSのルールに従わなければならないのではなく、他の者も受け入れる柔軟さがPRECSSにはあります。
拡張可能-
スマートフォンなどスクリーンサイズの狭い場合のみに有効なクラスが必要になった場合は、「.sm_**」という接頭辞を持つグループを作るのもよいでしょう。
PRECSSは基本的にそれぞれ独自の接頭辞を持つ6つのグループから成り立っていますが、必要に応じて拡張も可能です。
Basics
PRECSSはCSSの設計手法ではありますが、実際のプロジェクトで定義されるコーディング規約のなるべく多くをカバーするため、コードの書き方やクラス名に使用する単語の指針についても言及しています。
記法
コーディングスタイル
CSSの記述方法それ自体については、基本的にGoogle HTML/CSS Style Guide、Principles of writing consistent, idiomatic CSSに則ることを推奨します。これはなるべく世界的に有名な規則を採用することで、コードが他人や他社に渡ってもなるべく差異が出ないことを目的としています。
divの終了タグにはなるべくコメントを
div要素を使用する際は、なるべく終了タグに対応するクラス名を記載した閉じコメントを付けることを推奨します。これはdiv要素のネストが深くなったり、また何らかの理由でインデントが崩れた際にどの開始タグに対応するのかを把握しやすくするためです。
手動でコメントを追加するのは手間ですが、Emmetで展開前の文字列の末尾に「|c(半角のパイプと小文字のc)」と付けて展開することにより、自動的にクラス名に対応したコメントを終了タグの後に出力できます。
.hoge|c
↓ Emmetで展開
<div class="hoge">
...
</div>
<!-- /.hoge -->
命名規則
PRECSSでは、意図的に記述する全てのクラスに種類に応じた2文字の接頭辞を付加します。
接頭辞及び単語間はモジュールの構造に基づきスネークケースで結合し、1つの構造内に複数の単語がある場合はその部分のみキャメルケースを使用します。つまりPRECSSにおいてアンダースコアは、構造的な階層を表す役割を担っています。
基本的にIDセレクタは使用せず、クラスセレクタを使用します。
<!-- 単語をハイフンで結合した悪例 -->
<div class="bl_half-media">
<img class="bl_half-media_img">
<div class="bl_half-media_desc">
...
</div>
<!-- /.bl_half-media_desc -->
</div>
<!-- /.bl_half-media -->
<!-- 単語をアンダースコアで結合した悪例 -->
<div class="bl_half_media">
<img class="bl_half_media_img">
<div class="bl_half_media_desc">
...
</div>
<!-- /.bl_half_media_desc -->
</div>
<!-- /.bl_half_media -->
<div class="bl_halfMedia">
<img class="bl_halfMedia_img">
<div class="bl_halfMedia_desc">
...
</div>
<!-- /.bl_halfMedia_desc -->
</div>
<!-- /.bl_halfMedia -->
また各モジュールの子要素は、基本的にモジュールのルート要素の名前のみを継承し、アンダースコアの後に子要素の名前を続けます。例えば子要素の中に子要素がネストされている際も、ネストされている子要素はあくまでルート要素の名前のみを継承します。
<div class="bl_halfMedia">
<img class="bl_halfMedia_img">
<div class="bl_halfMedia_desc">
<h3 class="bl_halfMedia_ttl"> ... </h3>
<p class="bl_halfMedia_txt"> ... </p>
</div>
<!-- /.bl_halfMedia_desc -->
</div>
<!-- /.bl_halfMedia -->
ただし、
- 親子関係を意図をもって明確に定義したい
- モジュールが大きいため、子要素の名前の重複を避けたい
のいずれかに該当する場合は、 ネストされた子要素のクラス名に直近の親要素の名前を含めることも許容します。
<div class="bl_halfMedia">
<img class="bl_halfMedia_img">
<div class="bl_halfMedia_desc">
<!-- ○ 「bl_halfMedia_desc」を継承 -->
<h3 class="bl_halfMedia_desc_ttl"> ... </h3>
<!-- ○ 「bl_halfMedia_desc」を継承 -->
<p class="bl_halfMedia_desc_txt"> ... </p>
</div>
<!-- /.bl_halfMedia_desc -->
</div>
<!-- /.bl_halfMedia -->
汎用的に使用可能な名前
- _wrapper
- _inner
- _header
- _body
- _footer
これらは後述する5つのグループいずれにおいても、必要に応じて汎用的に使用することができます。
_wrapperはモジュールのルート要素の親クラスとして使用することを想定しており、子要素ではありませんが「そのモジュールに属する」として、子要素と同じようにアンダースコアで結合します。
<div class="bl_halfMedia_wrapper">
<div class="bl_halfMedia">
<div class="bl_halfMedia_inner">
<div class="bl_halfMedia_header">
...
</div>
<!-- /.bl_halfMedia_header -->
</div>
<!-- /.bl_halfMedia_inner -->
</div>
<!-- /.bl_halfMedia -->
</div>
<!-- /.bl_halfMedia_wrapper -->
単語を省略する場合
各モジュールに独自の名前を持たせる設計は非常に素晴らしいアイディアですが、命名が冗長になってしまうのは避けられない問題です。
PRECSSでは特に規則を設けませんが、単語を省略する場合はGoogle HTML/CSS Style Guideに基づくことを推奨します。
また2語以上で1つのまとまりを表す語群は、それぞれの頭文字の大文字のみで表現することも推奨します。ただし、ある程度一般的であったり、連続するパターンがあることが望ましいでしょう。
その他、一般的に使われる語を参考として併せて提示します。
- 一般的な例
- ・mainVisual→MV
- 連続するパターンの例
- ・northEurope→NE
- ・northAmerica→NA
- ・southAmerica→SA
- よく使われる省略語
-
- category(ies) → cat(s)
- column → col
- content(s) → cont(s)
- level → lv
- version → v
- section → sect
- description → desc
- button → btn
- clearfix → cf
- image → img
- number → num
- title → ttl
- text → txt
- left → l
- right → r
- small → smまたは
- medium → mdまたは
- large → lgまたは
- reverse → rev
シリーズを形成する場合
類似しているものはなるべく意味のある、または目的に即した命名を推奨しますが、ときに名前の付け分けが難しい場合もあるでしょう。そのような際は連番を付けて管理することも許容します。ただしその場合、1つ目のものには連番を付けません。
これは、仮に「ひとつ目にすべて連番を付ける」としてしまうと、後からシリーズを形成するようになった際、ひとつ目のものに連番を付け直す手間が発生してしまうからです。
<div class="bl_halfMedia1">...</div>
<div class="bl_halfMedia2">...</div>
<div class="bl_halfMedia3">...</div>
<div class="bl_halfMedia">...</div>
<div class="bl_halfMedia2">...</div>
<div class="bl_halfMedia3">...</div>
Documentation
基本設計
PRECSSは下記の6つのグループから構成されています。
※ソースコードは最低限解説に必要な記述のみを記載しています。
1.ベース
接頭辞:なし
ベースグループはSMACSSと同じように扱います。reset.cssやnormalize.cssによる素地作りの他、サイト全体にまつわるスタイルを要素セレクタに直接指定します。
またPRECSSでは、特定のスコープ内における限定的なベーススタイルの適用も許容します。
例えば「ヘッダー内のリンクは全て白色だが、フッター内は青色に統一したい」という場合に、限定的なベーススタイルを使用します。ただし詳細度を高める行為であるため、十分注意して使用してください。
body {
font-family: serif;
}
a {
color: #1565c0;
text-decoration: none;
}
img {
max-width: 100%;
vertical-align: top;
}
// 限定的なベーススタイルの例
.ly_header a {
color: #fff;
}
.ly_footer a {
color: blue;
}
2.レイアウト
接頭辞:ly_(layoutの略)
ヘッダー、ボディエリア、メインエリア、サイドエリア、フッター等の大きなレイアウトを形成する要素に使用します。
原則としてこのグループには、レイアウトに関わるスタイリング(gridやflex、width、margin、padding、floatなど)しか行いません。あくまでコンテンツが入る「枠」を定義するだけで、コンテンツは後述するモジュールグループで作成します。
ただし「ヘッダーの背景色は黒」など「枠」と「あしらい」の粒度が一致している場合は、必要に応じてレイアウト以外のスタイリングを行うことも許容します。
<header class="ly_header">
<div class="ly_header_inner">
...
</div>
<!-- /.ly_header_inner -->
</header>
<div class="ly_cont ly_cont__col"> <!-- モディファイアについては後述します -->
<main class="ly_cont_main">
...
</main>
<aside class="ly_cont_side">
...
</aside>
</div>
<!-- /.ly_cont ly_cont__col -->
<footer class="ly_footer">
<div class="ly_footer_inner">
...
</div>
<!-- /.ly_footer_inner -->
</footer>
.ly_header {
padding-top: 10px;
padding-bottom: 10px;
background-color: #f4d9d9;
}
.ly_header_inner {
max-width: 1230px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.ly_cont {
max-width: 1230px;
padding: 60px 15px;
margin-right: auto;
margin-left: auto;
}
.ly_cont.ly_cont__col { // モディファイアについては後述します
display: flex;
}
.ly_cont_main {
flex: 1;
margin-right: 3.25203%;
}
.ly_cont_side {
flex: 0 0 260px;
}
.ly_footer {
padding-top: 20px;
padding-bottom: 20px;
background-color: #dbd9e5;
}
.ly_footer_inner {
max-width: 1230px;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
3.モジュール
PRECSSでは再利用性の高いコードをモジュールとして管理します。モジュールは大きさよって
- ブロックモジュール
- エレメントモジュール
の2つの粒度に分けて定義しています。
3-1.ブロックモジュール
接頭辞:bl_(blockの略)
ブロックモジュールは、そのモジュール特有の幾つかの子要素を持ち、また後述するエレメントモジュールや他のブロックモジュールを含むこともできます。一つの塊として持ち運び可能な要素群を形成し、BEMで例えるならば「複数のElementを持つBlock」と言い換えることができます。
それら複数の子要素やエレメントモジュールを1つの塊としてまとめ、サイト内のどこでも使用できるようにすることがブロックモジュールの基本的な考え方です。webサイトの中核を担うもので、多くのモジュールはこのブロックモジュールに該当します。
以下はブロックモジュールの例です。
<div class="bl_jumbotron" style="background-image: url('https://picsum.photos/1200/600');">
<div class="bl_jumbotron_inner">
<p class="bl_jumbotron_ttl">TITLE HERE</p>
</div>
<!-- /.bl_jumbotron_inner -->
</div>
<!-- /.bl_jumbotron -->
<div class="bl_media">
<figure class="bl_media_imgWrapper">
<img alt="Dummy Image" src="https://picsum.photos/640/426">
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl">TITLE HERE</h3>
<p class="bl_media_txt">...</p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
.bl_jumbotron {
width: 100%;
height: 300px;
background-position: center center;
background-size: cover;
}
.bl_jumbotron_inner { ... }
.bl_jumbotron_ttl { ... }
.bl_media {
display: flex;
align-items: center;
}
.bl_media_imgWrapper { ... }
.bl_media_imgWrapper > img { ... }
.bl_media_body { ... }
.bl_media_ttl { ... }
.bl_media_txt { ... }
子要素のクラス名は、モジュールのルート要素の名前のみを継承するのが基本です。しかしBasics内の命名規則セクションでも触れましたが、
- 親子関係を意図をもって明確に定義したい
- モジュールが大きいため、子要素の名前の重複を避けたい
のいずれかに該当する場合は、 ネストされた子要素のクラス名に直近の親要素の名前を含めることも許容します。
次のコードは4・8・9行目でそれぞれ直近の親要素のクラス名を引き継ぎ、構造(依存)を明確にするとともにクラス名の重複を回避しています。
<dl class="bl_faq">
<dt class="bl_faq_q">
<span class="bl_faq_icon">Q</span>
<span class="bl_faq_q_txt">Question Here</span>
</dt>
<dd class="bl_faq_a">
<span class="bl_faq_icon">A</span>
<div class="bl_faq_a_body">
<p class="bl_faq_a_txt">Answer Here</p>
</div>
<!-- /.bl_faq_a_body -->
</dd>
</dl>
.bl_faq { ... }
.bl_faq_q,
.bl_faq_a { ... }
.bl_faq_q { ... }
.bl_faq_q_txt { ... }
.bl_faq_a { ... }
.bl_faq_icon { ... }
.bl_faq_q .bl_faq_icon { ... }
.bl_faq_a .bl_faq_icon { ... }
.bl_faq_a_body { ... }
.bl_faq_a_txt { ... }
またPRECSSではSMACSSと同様に、子要素のタグにスタイルを直接指定することを許容します。ただしその際はなるべく子結合子で指定をすること、divやspan等のセマンティックでない要素は独自のクラスを付加することを推奨します。
また、あまり階層が深くなり過ぎるとコードの見通しや詳細度の管理に影響が出る恐れがあるため、ネストは3階層程度までを目処とし、それ以上になりそうな場合は適宜クラスを付加することを推奨します。
<ul class="bl_bulletList">
<li>list 1</li>
<li>list 2</li>
</ul>
.bl_bulletList {
padding-left: 1em;
margin-bottom: 20px;
}
.bl_bulletList > li {
list-style-type: circle;
}
ブロックモジュールに適用可能なスタイル
注意点として、ブロックモジュールにはレイアウトに関わらないスタイルのみの適用を基本とします。
レイアウトに関わるスタイル、即ち
width(100%は例外)
や
position: absolute / fixed / sticky、
top / right / bottom / left
、
margin
、
float
等は、コンテキストまたはラッパーモジュールからスタイルを適用します。コンテキストの例についてはAdvance Caseを、ラッパーモジュールの例についてはこの後の「複数カラムの例」のコードを参考にしてください。
即ちブロックモジュールの幅は、なるべく初期値のまま(多くはブロックレベルであるため、横いっぱいに広がる)あることが望ましく、このことにより高い拡張性と移植性が実現されます。
ただし例外として、デザイン上でモジュールの上下間の余白に統一されたルールがある場合は、
margin-top / bottom
の適用を許容します。
これらについては、カラムを形成するブロックモジュールを例に考えると理解しやすいでしょう。
1カラムの例
この状態ブロックモジュールの基本状態であり、もちろん単体で使用できるほか、レイアウトに関する指定をしないことで何処へでも移植することが可能です。
<div class="bl_card">
<figure class="bl_card_imgWrapper">
<img alt="Dummy Image" src="//placehold.jp/E13626/fff/1280x718.png?text=Card 1">
</figure>
<div class="bl_card_body">
<h3 class="bl_card_ttl">TITLE HERE</h3>
<p class="bl_card_txt"> ... </p>
</div>
<!-- /.bl_card_body -->
</div>
<!-- /.bl_card -->
.bl_card {
width: 100%;
background-color: #fff;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
}
.bl_card_imgWrapper { ... }
.bl_card_imgWrapper > img { ... }
.bl_card_body { ... }
.bl_card_ttl { ... }
.bl_card_txt { ... }
複数カラムの例
bl_cardUnit
というラッパーモジュールを作成してでラップすることにより、他の箇所で使われている既存のブロックモジュールに影響を与えることなく、またCSSの修正の必要もなく再利用することができます。
さらに
bl_cardUnit
自体が高次のブロックモジュールとなったため、今後は
bl_cardUnit
を他に移植することも可能となります。
またwidthをカラム数に応じたモディファイアから制御することで、高い拡張性を実現します。CSSにおいて、元の .bl_cardモジュールのスタイリングは一切調整していないことに着目してください。
<ul class="bl_cardUnit bl_cardUnit__col3">
<li class="bl_card"> ... </li>
<li class="bl_card"> ... </li>
<li class="bl_card"> ... </li>
</ul>
<ul class="bl_cardUnit bl_cardUnit__col4">
<li class="bl_card"> ... </li>
<li class="bl_card"> ... </li>
<li class="bl_card"> ... </li>
<li class="bl_card"> ... </li>
</ul>
.bl_cardUnit {
display: flex;
flex-wrap: wrap;
}
// 3カラム
.bl_cardUnit.bl_cardUnit__col3 {
margin-bottom: 10px;
}
.bl_cardUnit__col3 > .bl_card {
width: 31.707%;
margin-right: 2.43902%;
margin-bottom: 30px;
}
.bl_cardUnit__col3 > .bl_card:nth-of-type(3n) {
margin-right: 0;
}
// 4カラム
.bl_cardUnit.bl_cardUnit__col4 {
margin-bottom: -20px;
}
.bl_cardUnit__col4 > .bl_card {
width: 23.78%;
margin-right: 1.62602%;
margin-bottom: 20px;
}
.bl_cardUnit__col4 > .bl_card:nth-of-type(4n) {
margin-right: 0;
}
@media screen and (max-width: 768px) {
.bl_cardUnit.bl_cardUnit__col3 {
margin-bottom: -20px;
}
.bl_cardUnit.bl_cardUnit__col4 {
margin-bottom: -15px;
}
.bl_cardUnit > .bl_card {
width: 100%;
margin-right: 0;
margin-bottom: 20px;
}
}
極端な例を示すと、これらをメインエリア・サイドエリアそれぞれに移植するとこのような形になります。きちんと設計しておけば、ここまで極端なことをしても期待通りにモジュールが動作します。
以上のようにブロックモジュールというのは非常に柔軟で、パワフルにサイト内を駆け回ることができます。
今回の例では
.bl_cardUnit
という専用のラッパーモジュールを作成しましたが、「ブロックモジュールのwidthは初期値のまま(または100%)にしておく」ということを遵守していれば、他のCSSフレームワークが提供するグリッドシステムと連携することも可能です。
ただし、その高い移植性を維持するためには「レイアウトに関するスタイルは1つ上のレイヤー(ラッパーモジュールまたはコンテキスト)から制御する」という少し特殊なテクニックが必要で、慣れないうちは苦労するでしょう。
コンテキストを利用した例、さらに複雑なブロックモジュールの移植については、下部にAdvance Caseとしてまとめたので、そちらをご参照ください。
また下記に、ブロックモジュールを命名する際に役立つ粒度の指針を提供します。
PRECSSにおいてブロックモジュールは非常に重要な役割を担うため、これらの単語を分かりづらい形に省略することは推奨しません。
必ずしもこれらの名前を含まなければならない訳ではありませんが(私自身、基本単位である「block」という単語は含まないことが多いです)、これらを参考にすることは、あなたがブロックモジュールを自在に操ることを強力に手助けしてくれると思います。
現実として、Unit以上の大きさを使用することはあまり無いでしょう。
ブロックモジュールにおける概念・命名の粒度
- Block - ブロックモジュールの基本単位。そのモジュール特有の複数の子要素や、エレメントモジュールを含む(例:bl_card)
- Unit - Blockの集まり(例:bl_cardUnit)
-
Box - Unitの集まり「Box」という単語は純粋にモジュール名に含みたい場合があるので、非推奨としました。 - Container - Unitの集まり
3-2.エレメントモジュール
接頭辞:el_(elementの略)
ボタンやラベル、見出し等の最小単位のモジュールで、どこにでも埋め込むことが可能なモジュールです。
命名は極力汎用的なものを推奨します。これはどのようなコンテンツが入っても、名前と内容が乖離しないための措置です。
「極力汎用的な命名を」というのはBEMとは真逆の方向を向いている思想ですが、現実として、例えば全ての色に意味を持たせた命名は困難です。
※もちろんプロジェクトのデザインシステムにおいて、スタイルと命名がセマンティックに一致する場合は、その限りではありません(色がきちんとテーマカラーとして定義されていたり、商品やブランドがテーマカラーを持つ場合など)。
類似するスタイルのエレメントモジュールが複数存在する場合は、OOCSSにおけるストラクチャとスキンの考え方を使用します。
個別のスキンは後述するモディファイアで実装しますが、ベースクラスに標準状態として予めスキンを適用することでモディファイアを減らすことも推奨します(次のコードにおける.el_label、.el_btn)。
<div class="bl_media hp_mb30">
<figure class="bl_media_imgWrapper">
...
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl"> ... </h3>
<ul class="bl_media_labels">
<li>
<!-- 名前が汎用的でないため推奨しない -->
<span class="el_label el_label__news">News</span>
</li>
<li>
<!-- 名前が汎用的でないため推奨しない -->
<span class="el_label el_label__blog">Blog</span>
</li>
</ul>
<p class="bl_media_txt"> ... </p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
<p class="hp_tac">
<!-- 名前が汎用的でないため推奨しない -->
<button class="el_btn el_btn__cancel" type="button">Cancel</button>
<button class="el_btn el_btn__submit" type="button">Submit</button>
</p>
<div class="bl_media hp_mb30">
<figure class="bl_media_imgWrapper">
...
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl"> ... </h3>
<ul class="bl_media_labels">
<li>
<span class="el_label">News</span>
</li>
<li>
<span class="el_label el_label__blue">Blog</span>
</li>
</ul>
<p class="bl_media_txt"> ... </p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
<p class="hp_tac">
<button class="el_btn el_btn__blue" type="button">Cancel</button>
<button class="el_btn" type="button">Submit</button>
</p>
.el_label {
display: inline-block;
padding: .2em .5em;
background-color: #de5b5b;
color: #fff;
font-size: .75rem;
}
.el_label.el_label__blue {
background-color: #308EDE;
}
.el_btn {
display: inline-block;
width: 200px;
max-width: 100%;
padding: 10px 5px;
border-radius: 5px;
box-sizing: border-box;
background-color: #E18700;
box-shadow: 0 3px 0 #B85F29;
color: #fff;
text-align: center;
text-decoration: none;
transition: .25s;
}
.el_btn.el_btn__blue {
background-color: #308EDE;
box-shadow: 0 3px 0 #2572B1;
}
.hp_tac {
text-align: center !important;
}
例えばもし、この青がサイトにおけるアクセントカラーとデザインシステムで定義されている場合は、モディファイア名を「.el_btn__accent / .el_btn__accentColor」とすることも推奨します。そうすると、万が一アクセントカラーが青から変更になっても、モディファイア名を変える必要はありません。
レイアウトについて
移植性の維持のため、ブロックモジュールと同じくエレメントモジュール自体にレイアウトのためのスタイルを当てることは推奨しません。
コンテキストから制御するか、もしくは上記の
.hp_tac
のように何らかの親要素を使用するのが理想です(
.hp_
については次のヘルパーセクションで解説します)。
ただし、ブロックモジュールに比べエレメントモジュールはサイト全体でバリエーションに限りがあることが多いため、widthの直接指定、及びモディファイアでサイズのパターンを作成することは十分許容します。例えば、ボタンのサイズは多くのデザインにおいてパターンがあるでしょう。
ただし、例えば
- レスポンシブ・ウェブデザインを採用し
- スマートフォンを想定するビューでは、ボタンを横幅100%にする
となった際に、モディファイアの名前を
__w250
(width: 250pxの意)と数値を固定すると、
__w250
と付いているのにSPビューでの実際の表示は横幅100%という事態が発生します。
このように名前と実際の大きさに矛盾が発生すると破綻への第一歩となることは留意しておいてください。
状況にもよりますが、解決策としては
- サイズを指定する場合は、モディファイア名は
__sm・__md・__lgなどとする - サイズを指定せずコンテンツに応じて可変にする場合は、
display: inline-block;としておく
などすると、応用が効くでしょう。
あまりエレメントモジュールのモディファイアパターンにハマらない場合は、もちろん移植先のモジュールのElementとしての指定か、子(孫)セレクタを使用してスタイリングすることも可能です。
次のコードはコンテキストから制御した例と、モディファイアで制御した例です。
<header class="ly_header">
<div class="ly_header_inner">
<!-- コンテキストから制御した例 -->
<a class="el_btn" href="#">.el_btn</a>
</div>
<!-- /.ly_header_inner -->
</header>
<p class="hp_tac">
<!-- モディファイアで制御した例 -->
<a class="el_btn el_btn__lg" href="#">.el_btn.el_btn__l</a>
</p>
// コンテキストから制御した例
.ly_header_inner > .el_btn {
display: block;
margin-left: auto;
width: 150px;
}
// モディファイアで制御した例
.el_btn.el_btn__l {
width: 400px;
padding-top: 1em;
padding-bottom: 1em;
}
@media screen and (max-width: 768px) {
.el_btn.el_btn__l {
width: 100%;
}
}
3-3.モディファイア
命名規則:基となるクラス名__モディファイア名
既に何度か言及していますが、
- あしらいが変わる
- 大きさが変わる
- 一定の規則に従って振る舞いが変わる(カラム等)
などの場合はモディファイアによる上書きをPRECSSでは推奨し、アンダースコア2つの後にモディファイア名を付与します。
「何をするモディファイアなのか」を明確にするために、モディファイア名は「
__backgroundColorOrange
」のように「
__keyValue
」の形を基本としますが、おおよそ想像がつくものであればkeyの省略が可能です。
また名前が長くなるのを避けたい場合は、Emmetのショートハンドに準じて「
__backgroundColorOrange
」を「
__bgcOrange
」のように省略することも可能です。
多くはモジュールグループにおいて使用されますが、状況によってはレイアウトグループなど、他のグループに使用することもできます。
注意点としてモディファイアでスタイルを上書きする際は、基本的に複数クラスを使用して、詳細度を高めることを推奨しています。
「スタイルを上書きする」ということは意図的なアクションであり、であればCSSの読み込み順でスタイルに変化が出てしまうのは好ましくありません。
これもBEMとは異なる思想ですが、「CSSが自分の手を離れても、CSSのルールセットの順番が変わることは絶対ない」と言い切ることは私にはできません。
そのためweb開発者の都合を優先するのではなく、あくまで「スタイルを上書きする」という目的に立ち返り、想定外の事態が発生してもなるべくサイトが壊れないことを優先し、このようなルールとしています。
また「状態(active, disabledなど)」を変更する際にももちろんモディファイアを使用することができますが、基本的には後述するプログラムグループで変更を制御することをPRECSSでは推奨しています。
エレメントモジュールの例
.el_btn__orange {
background-color: orange;
}
.el_btn {
background-color: white;
}
//適用されるカラー:white
//何らかの都合でスタイルの読み込み順が変わったとき、モディファイアが機能しなくなってしまう
.el_btn {
background-color: white;
}
.el_btn.el_btn__orange {
background-color: orange;
}
ブロックモジュールの例
ブロックモジュールのモディファイアによる変更パターンについては、
- 特定の子要素のみにモディファイアを付加する
- ブロックモジュールのルート要素にモディファイアを付加する
の2通りのパターンがあげられます。
前者の場合はセオリー通り複数クラス指定をすることにより、後者の場合はモディファイア名と子(孫)結合子を使用することにより、詳細度を高めます。
後者の方法はルート要素に付与した1つのモディファイアで、複数の子要素のスタイルを変更したい場合に最適です。
次の例は
- 特定の子要素のみにモディファイアを付加する → 画像にボーダーを付ける
- ブロックモジュールのルート要素にモディファイアを付加する → 左右反転させる
のデモです。
<!-- 特定の子要素のみにモディファイアを適用する場合 -->
<div class="bl_media">
<figure class="bl_media_imgWrapper bl_media_imgWrapper__bordered">
<img alt="Dummy Image" src="https://picsum.photos/640/426">
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl">TITLE HERE</h3>
<p class="bl_media_txt"> ... </p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
<!-- 子要素複数に対してスタイルを上書きする例 -->
<div class="bl_media bl_media__rev">
<figure class="bl_media_imgWrapper">
<img alt="Dummy Image" src="https://picsum.photos/641/426">
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl">TITLE HERE</h3>
<p class="bl_media_txt"> ... </p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
// 特定の子要素のみにモディファイアを適用する場合
.bl_media_imgWrapper.bl_media_imgWrapper__bordered {
padding: 5px;
border: 1px solid #000;
}
// 子要素複数に対してスタイルを上書きする例
.bl_media.bl_media__rev {
flex-direction: row-reverse;
}
.bl_media__rev .bl_media_imgWrapper {
margin-right: 0;
}
.bl_media__rev .bl_media_body {
margin-right: 3.33333%;
text-align: right;
}
後者はさておき、前者の特定の子要素のみに変更を加えたいときに、モジュールのルート要素にモディファイアを付加することは推奨しません。モディファイアのスコープが広がり、他の子要素まで制御している可能性があるように見えてしまうからです。
<div class="bl_media bl_media__imgBordered">
<figure class="bl_media_imgWrapper bl_media_imgWrapper__bordered">
<img alt="Dummy Image" src="https://picsum.photos/640/426">
</figure>
<div class="bl_media_body">
<h3 class="bl_media_ttl">TITLE HERE</h3>
<p class="bl_media_txt"> ... </p>
</div>
<!-- /.bl_media_body -->
</div>
<!-- /.bl_media -->
また繰り返しになりますが、ブロックモジュールそれ自体の大きさの変化に対して、モディファイアを付加することはあまり推奨しません。ブロックモジュールの表示サイズは移植先のコンテキストにより頻繁に変わる可能性があり、ブロックモジュールはエレメントモジュールに比べバリエーションが不定になりがちだからです。
これも繰り返しになりますが、ブロックモジュールが他所のモジュール内にネストされ、かつレイアウトの調整が必要な場合は、コンテキストからの制御を推奨します。(詳しくはAdvance Caseにて解説しています)
4.ヘルパー
接頭辞:hp_(helperの略)
基本的に1つのスタイルのみで、意図的な上書きのため
!important
を付加することを推奨します。命名規則に関してはモディファイアと同様「
keyValue
」の形を取り、省略する場合はEmmetのチートシートに準ずることを推奨します。
その他の規則として
- px以外の単位の場合は分かりやすい頭文字で表現
- 小数点はアンダースコアで表現
- ネガティブな値はショートハンドを大文字で表現
- ショートハンドの後に文字列が続く場合はキャメルケースで表現
とします(これらの規則はモディファイアの命名にも概ね有効です)。
ただし1つの要素に対しヘルパーを多用し過ぎると、style属性を使用していることと変わりが無くなってしまうので、ヘルパーが3つ以上になった場合はモジュール化を検討するべきでしょう。
基本的に1スタイルのみのため、1行で記載することを許容します。 また挙動が限定的でかつ明確な場合のみ、1つ以上のスタイルであってもヘルパーで処理することが可能です。
.hp_db { display: block !important; }
.hp_tac { text-align: center !important; }
.hp_w400 { width: 400px !important; }
.hp_w50p { width: 50% !important; }
.hp_lh1_5 { line-height: 1.5 !important; }
.hp_MT1e { margin-top: -1em !important; }
.hp_bgcWhite { background-color: #fff !important; }
//1つ以上のスタイルの例
.hp_centering {
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
}
clearfixについて
そもそも、もうあまりfloatでレイアウトを行うこと自体少ないと思いますが、万が一floatを用いてレイアウトを行う場合、PRECSSではfloatの解除にclearfixを推奨します。これは、floatの解除のためだけに本来の目的とは関係の無いスタイルを記述することを忌避するためです。
特にoverflowは本来の目的で使われる場合もあり、CSSコードだけ見たときに、それがfloatの解除のためなのか、本来の目的のためなのかわからない、といった状況は好ましくありません。
またPRECSSのルールに当てはめればclearfixもヘルパーとして定義することが可能ですが、clearfixはそれ自体が充分に一般的であり誰が見ても役割を把握できるため、無理に接頭辞を付けなくても構いません。
5.ユニーク
接頭辞:un_(uniqueの略)
ある1ページでしか使用されていないことを明示するグループです。ECSS以外のCSSアーキテクチャで、不要になれば臆せず捨てられるスタイルの目印を用意しているのが、PRECSSの大きな特徴の1つです。
そのページでしか使われていないため、改修や運用の際に影響範囲を気にせずにスタイルを編集してよい目印になっています。
モジュールの大きさも自由です。小さくても大きくてもかまいせん。
例えばPRECSSの扉ページのような特別なページに使用するのもいいですし、通常のページ内でもモジュール設計から外れる場所(例えば
postion: absolute;
を乱発するような)に使用するのもいいでしょう。
つまりユニークグループは、あらゆるイレギュラーのための万能な回避策です。
何か迷ったら、とりあえずユニークグループを使ってください。いつでも誰でも、すぐに手直ししてよい目印です。
ただし濫用し過ぎると拡張性に欠けるため、あくまでイレギュラーのための措置であることを覚えておいてください。
またCSSには、どのページで使用しているかコメントを残しておくとよりよいでしょう。
<div class="un_siteRoot_wrapper">
<section class="un_siteRoot">
<p class="un_siteRoot_logo"><img src="images/icon.svg" alt="PRECSS logo"></p>
<h1 class="un_siteRoot_ttl">PRECSS</h1>
<p class="un_siteRoot_link"><span class="is_deactive">English</span> / <a href="/ja/">日本語</a></p>
</section>
</div>
//トップページ(/)に使用
.un_siteRoot_wrapper {
position: relative;
top: 33vh;
text-align: center;
}
.un_siteRoot {
display: inline-block;
}...
6.プログラム
PRECSSではJavaScript等のプログラムで要素にタッチする際、または状態を管理する際、専用のクラスを付加しモジュールのクラスとは分離することを推奨します。
接頭辞:js_(JavaScriptの略)
JavaScriptにて要素を取得するためのクラスです。
接頭辞:is_(英語のbe動詞のisから)
要素の状態を管理するためのクラスです。必ず適用されなければならないスタイルであるため、
!important
の使用も推奨します。
状態の命名は
is_active
とシンプルに記述することが可能ですが、他の箇所にも影響を及ぼさないよう必ず複数クラスでスタイルを適用する必要があります。
またプログラムグループを用いて実現したいような挙動に関しては、これら2つの接頭辞ではなく、カスタムデータ属性やWAI-ARIAを使用することも許容します。
<dl class="bl_accordion js_accordion">
<dt>
<button class="bl_accordion_btn js_accordion_ttl is_active" type="button">TITLE HERE</button>
</dt>
<dd class="bl_accordion_body js_accordion_body is_active">
<p class="bl_accordion_txt">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nesciunt, voluptates!
</p>
</dd>
</dl>
.js_accordion_body {
display: none;
}
//他の箇所にまで影響を及ぼす可能性がある
.is_active {
display: block;
}
.js_accordion_body {
display: none;
}
.js_accordion_body.is_active {
display: block;
}
なお表示 / 非表示程度のシンプルな制御であれば、上記のコードの通り
.js_
接頭辞のクラスに対するスタイリングで事足ります。しかしモジュールによっては、「表示 / 非表示の状態によって、アイコンも変わる」など、複雑なスタイリングが必要なこともあるでしょう。
そういった場合は、
.js_
クラスと
.is_
クラスの組み合わせでなく、
.bl_
クラスと
.is_
クラスの組み合わせでスタイリングを行うことも可能です。今回のアコーディオンのCSSも、実際には次のようになっています。
// 横棒の生成
.bl_accordion_btn::before {
content: '';
position: absolute;
top: 50%;
right: 15px;
display: block;
width: 20px;
height: 2px;
background-color: currentColor;
transform: translateY(-50%);
}
// 縦棒の生成
.bl_accordion_btn::after {
content: '';
position: absolute;
top: 50%;
right: 24px;
display: block;
width: 2px;
height: 20px;
background-color: currentColor;
transform: translateY(-50%);
}
// .is_active時は縦棒を無くし、マイナスアイコンに見せる
.bl_accordion_btn.is_active::after {
content: none;
}
.bl_accordion_body {
display: none;
}
.bl_accordion_body.is_active {
display: block;
}
重要なのは、1つのモジュールに関するスタイリングで
.js_&.is_
と
.bl_&.is_
を混在させないことです。上記のコード例で言えば「アイコンのアクティブ表示は
.bl_&.is_
なのに、ボディのアクティブ表示は
.js_&.is_
となっている」という状況は混乱の元なので、1つのモジュール内においては
.is_
を
.js_
と組み合わせるか、
.bl_
と組み合わせるかは必ず統一しましょう。
7.オリジナル
その他、サイトの構築思想に応じて柔軟に接頭辞を追加できるのがPRECSSの特徴です。
例えばオリジナルのグリッドシステムを構築する場合、gridの略として
.gr_4
、
gr_6
やcolumnの略として
.cl_4
、
cl_6
といった接頭辞を追加することができます。
デスクトップ幅のみに有効なクラスは
.lg_**(largeの略)
、タブレット幅以下のみ有効なクラスは
.md_**(mediumの略)
、スマートフォン幅以下のみ有効なクラスは
.sm_**(smallの略)
とするのもいいでしょう。
またPRECSSの命名規則が使えるのは、HTML/CSS/JavaScriptに限りません。テンプレートエンジンやビルド環境、或いはCMSテンプレートにもPRECSSの命名規則を活かすことができます。
例えばWordPressのメソッドに依存してるmacroやmixinがある場合は、
wp_
という接頭辞を付けておくと、名前を見るだけでWordPressの処理が絡んでいることが予測できます。MovableTypeなら
mt_
、Drupalなら
dr_
、a-blog cmsなら
ac_
、HubSpot CMSなら
hs_
という具合になります。
言語やテンプレートエンジンによっては変数名にハイフンが使用できないものもありますが、PRECSSではハイフンを使用しないため、開発環境全体を通して命名規則を統一することも可能です。一定の法則に従っている限り、PRECSS はどのように拡張してもらっても構いません
Advance Case
モジュールが他のモジュールにネストされる場合
PRECSSでは一つのブロックモジュール内に他のブロックモジュール、またはエレメントモジュールがネストされることがあります。これは、モジュールが「どこでも再利用されるべきもの」である思想の下の正しい挙動です。
その際に直面する、主にレイアウトの問題について、幾つか指針を示します。
現在構築中のモジュールをアコーディオン(
.bl_accordion
)、既に他所で構築済みで、アコーディオンに埋め込みたいモジュールをメディア(
.bl_media
)としましょう。
基本的な原則は、以下の2つのみです。
- 埋め込まれたモジュール(メディア)のレイアウトは、コンテキスト(アコーディオン)から制御する
- 埋め込まれたモジュール(メディア)内の子要素は、コンテキスト(アコーディオン)から制御してはならない
原則1. 埋め込まれたモジュール(メディア)のレイアウトは、コンテキスト(アコーディオン)から制御する
まず1つ目の原則です。例えばアコーディオン内にメディアを埋め込んだ際、
- メディアの横幅を90%に
- かつ、左右中央揃えに
したいとしましょう。次のような表示です。
この実装方法に関しては、次のいずれかを推奨します。
- コンテキスト(アコーディオン)の子要素(BEMでいうElementのMix)を作成し、その要素にスタイリングをする
- コンテキスト(アコーディオン)のクラス名と埋め込まれたモジュール(メディア)のクラス名、子(孫)結合子を使用してスタイリングする
それぞれのパターンのコードの例を次に示します。
ⅰ.コンテキストの子要素を作成し、その要素にスタイリングをする
<dd class="bl_accordion_body js_accordion_body is_active">
<p class="bl_accordion_txt"> ... </p>
<!-- .bl_accordion_mediaの追加 -->
<div class="bl_accordion_media bl_media">
(bl_mediaの内容)
</div>
<!-- /.bl_accordion_media bl_media -->
</dd>
// メディアの元のスタイリング
.bl_media {
display: flex;
align-items: center;
}
// 今回追加したスタイリング
.bl_accordion_media {
width: 90%;
margin-right: auto;
margin-left: auto;
}
.bl_media
と同一のdiv要素に
.bl_accordion_media
を付与しました。スタイリングも
.bl_accordion_media
にしていますので、アコーディオンとメディアは疎結合な関係となり、仮にメディアのクラス名を
.bl_media
から変更しても、アコーディオン内のメディアの表示にはなんら影響しません。
ただしこのパターンにはちょっとした弱点があります。
CSSを見ると
.bl_media
と
.bl_accordion_media
は同等の詳細度であるため、同一のプロパティがある場合、後に宣言された方が優先されます。仮に
.bl_media
に
margin-bottom: 40px;
が既に指定されており、かつアコーディオン内では
margin-bottom: 20px;
を適用したければ、必ず
.bl_accordion_media
の宣言が後に来なければなりません。この宣言順の管理は、モジュールの数が増えれば増えるほどとても煩雑なものとなります。
本来はブロックモジュールにレイアウトに関わるプロパティがなるべく宣言されていないのが望ましいのですが、実際の現場ではそうもいかないこともあるでしょう。
このコンフリクトに関しては詳細度を高くすればひとまず解決しますので、次のように
:not()
擬似クラスを利用して解決することも可能です。
.bl_accordion_media:not(.hoge) {
margin-bottom: 20px; // こちらが適用される
}
.bl_media{
margin-bottom: 40px;
}
ただしこの方法はハックの域を出ませんので、よく思わない方もいるでしょう。他には、モジュールのルート要素をセレクタに含んで詳細度を高める方法もあります。
.bl_accordion .bl_accordion_media {
margin-bottom: 20px; // こちらが適用される
}
.bl_media{
margin-bottom: 40px;
}
ただし
.bl_accordion
の他の子要素は詳細度が単一のクラスセレクタのみでフラットに保たれていますので、CSSだけ見るとここだけ詳細度がいきなり高くなるのは、どうもひっかかります。
次の方法では、これらの問題を簡単に解決できます。
ⅱ.コンテキストのクラス名と埋め込まれたモジュールのクラス名、子(孫)結合子を使用してスタイリングする
<dd class="bl_accordion_body js_accordion_body is_active">
<p class="bl_accordion_txt"> ... </p>
<div class="bl_media">
(bl_mediaの内容)
</div>
<!-- /.bl_accordion_media bl_media -->
</dd>
.bl_accordion .bl_media {
width: 90%;
margin-right: auto;
margin-left: auto;
}
.bl_accordion
の名前と
.bl_media
、子孫結合子を使用してスタイリングしました。詳細度が高いため、
.bl_media
の元のスタイリングとの宣言順も気にする必要がありません。また新たにクラスを作成しなくて良いので、単純に楽です。
しかし一方で、この方法はアコーディオンとメディアが完全に疎結合とは言えません。メディアのクラス名が
.bl_media
から変更になると、先ほどの例では問題なかったのに対し、こちらの例ではアコーディオン内のメディアに対するスタイリングが効かなくなります。そのためモジュール名変更の際は、このような依存関係をプロジェクト全体に渡り横断検索し確認しなければなりません(とはいえ、モジュールの依存に関わらず普通は一度は横断検索するでしょうが)。
結局どちらの方法が良いとは一概には言えませんが、前者の方法では「メディアを埋め込むも、
.bl_accordion_media
のクラス名を付け忘れる」というリスクもありますので、運用中の事故やストレスが少ないのは後者でしょう。なるべく途中からモジュール名を変える必要がないよう、モジュールの命名時に熟考したいところです。
原則2. 埋め込まれたモジュール(メディア)内の子要素は、コンテキスト(アコーディオン)から制御してはならない
メディアをアコーディオンに埋め込んだ際にメディアの子要素に何らかの変更が必要な場合、
.bl_accordion .bl_media_**
という形でスタイルを上書きしてはいけません。また、メディアの子要素にアコーディオンの子要素クラスを追加することも推奨しません。
これはモジュールブロックそれ自体が、単独で完結すべき1つの塊という思想であるからです。塊それ自体のレイアウトがコンテキストにより挙動を変えるのは可能ですが、その子要素までもがコンテキストに影響されるのは好ましくありません。今回の例で言えばメディアの内部についてはメディアのみが責任を持つべきで、つまりブロックモジュール内は、そのブロックモジュール以外からの変更を受けない聖域と考えます。
メディアをアコーディオンへ埋め込んだ際にメディアの内部に変更が必要な場合は、メディアのモディファイア作成して処理します。ここでは、メディア内のフォントサイズが全体的に小さくなる例を示します。
<dd class="bl_accordion_body js_accordion_body is_active">
<p class="bl_accordion_txt"> ... </p>
<div class="bl_media bl_media__fzSm">
(bl_mediaの内容)
</div>
<!-- /.bl_accordion_media bl_media -->
</dd>
.bl_media__fzSm .bl_media_ttl {
font-size: .875rem;
}
.bl_media__fzSm .bl_media_txt {
font-size: .8125rem;
}
このように記述する最大のメリットは、責任範囲の明確なコードを維持できることにあります。仮にコンテキスト(アコーディオン)からメディア内の子要素に対してコントロールを行っていると、アコーディオンの改修に際しメディアの子要素に不具合が起きる可能性があります。そのようなコンテキストと埋め込まれたモジュールが密結合な状態は好ましくありません。
モジュールの責任範囲を明確にしておくことのメリットは、メンテナンス性の高さだけではありません。仮にフォントサイズの小さいメディアをアコーディオンとは違う箇所で使用したい場合も、モディファイアを付ければ、いつでもどこでもすぐに同じ状態を再現できます。