CSS設計におけるコンポーネントのスタイルを上書きする方法を考える
CSS設計を考える上で、「使用する場所に応じてスタイルの一部を変更したいコンポーネント」の扱いをどうするかは大きなテーマの一つだと感じます。
方法は一つではないため、そのプロジェクトにおける最適な方法を選び、プロジェクト全体を通して一貫した書き方を維持することが重要だと考えます。この記事では4つの方法について考察しています。
モディファイアでスタイルを上書きする方法
一般的にはよく推奨されている方法です。コンポーネントにモディファイアを付けて、そのモディファイに応じてスタイルを変更するパターンです。
以下の例では、c-button
の基本スタイルに対して、c-button--reverse
やc-button--icon-small
といったモディファイアを追加してスタイルを上書きしています。
<div class="p-about__button"> <a href="./about/" class="c-button c-button--reverse c-button--icon-small"> <span class="c-button__text">もっと見る</span> <span class="c-button__icon"><img src="./img/common/icon_arrow.svg" alt=""></span> </a></div>
.c-button { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px;}
.c-button__icon { width: 25px;}
// c-button--reverseに関するスタイル.c-button--reverse { grid-template-columns: auto minmax(0, 1fr); // grid-template-columnsを上書き}
.c-button--reverse > .c-button__icon { order: -1; // orderを追加}
// c-button--icon-smallに関するスタイル.c-button--icon-small > .c-button__icon { width: 15px; // widthを上書き}
この方法のメリットとデメリットは以下の通りです。
メリット | デメリット |
---|---|
|
|
「再利用性」「スタイルの意図」「コンポーネントの独立性」が大きなメリットだと感じます。特に再利用性を考慮する場合にはモディファイアでの上書きが適しており、真っ先に検討すべき方法だと考えます。
一方で「微妙に異なる共通パーツが無数に存在するデザイン」ではモディファイア地獄となり、管理が大変になります。そのような場合には親要素からカスケーディングさせる方法が選択肢となるでしょう。
親要素からカスケーディングさせる3つの方法
親要素からスタイルをカスケーディングさせる方法では、先述した「再利用性」「スタイルの意図の明確さ」「コンポーネントの独立性」といった利点の多くが損なわれます。
それでも、この方法には明確なメリットがあり、プロジェクトによっては有効な選択肢となります。
最大のメリットは、柔軟なデザインに対応できる点です。親要素の数だけスタイルを上書きできるため、「コンポーネント設計が比較的緩めなデザインデータ」でも問題なく対応可能です。以下の3つの方法について詳しく説明します。
- 親要素のスタイルで詳細度を高める方法
- コンポーネントに親要素固有のクラスを追加する方法
- カスタムプロパティでスタイルを継承させる方法
①親要素のスタイルで詳細度を高める方法
親要素のクラスを頼りにコンポーネントのスタイルを上書きする方法です。
以下の例では親要素であるp-about__button
から子要素のコンポーネントであるc-button
のスタイルを変更しています。
<div class="p-about__button"> <a href="./about/" class="c-button"> <span class="c-button__text">もっと見る</span> <span class="c-button__icon"><img src="./img/common/icon_arrow.svg" alt=""></span> </a></div>
.c-button { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px;}
.c-button__icon { width: 25px;}
.p-about__button > .c-button { grid-template-columns: auto minmax(0, 1fr); // grid-template-columnsを上書き}
.p-about__card > .c-button .c-button__icon { order: -1; // orderを追加 width: 15px; // widthを上書き}
この方法のメリットとデメリットは以下の通りです。
メリット | デメリット |
---|---|
|
|
詳細度を低く保ちたい場合には避けたい方法です。
また.scss
でファイル分割をしている場合には、上書きしたスタイルをコンポーネント側のファイルに記述するか、親要素側のファイルに記述するかをあらかじめ決めておく必要があるでしょう。
②コンポーネントに親要素固有のクラスを追加する方法
次はコンポーネントの各要素に親要素固有のクラスを名を併記する方法です。以下のコードではp-about__button-link
、p-about__button-text
、p-about__button-icon
を追加しています。
<div class="p-about__button"> <a href="./about/" class="p-about__button-link c-button"> <span class="p-about__button-text c-button__text">もっと見る</span> <span class="p-about__button-icon c-button__icon"><img src="./img/common/icon_arrow.svg" alt=""></span> </a></div>
.c-button { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px;}
.c-button__icon { width: 25px;}
.p-about__button-link { grid-template-columns: auto minmax(0, 1fr); // grid-template-columnsを上書き}
.p-about__button-icon { order: -1; // orderを追加 width: 15px; // widthを上書き}
この方法のメリットとデメリットは以下の通りです。
メリット | デメリット |
---|---|
|
|
詳細度を同じ状態で保てる一方、CSSの読み込み順を意識する必要があります。上記の例では.c-button
よりも後に.p-about__button-link
のスタイルを読み込んでおく必要があります。
上書きは親要素のクラスに対して行うので、ファイル分割をしている場合でもコードの記述場所で迷うことはなくなるでしょう。
1つ前の「親要素のスタイルで詳細度を高める方法」のデメリットをかなり改善できたと言えるでしょう。
③カスタムプロパティで値を継承させる方法
先述の①と②のミックスのような方法です。
コンポーネントのルート要素に親要素由来のクラスを追加し、カスタムプロパティを使用してルート要素から子要素にスタイル適用する方法です。
以下の例ではp-about__button-link
クラスを追加しています。
<div class="p-about__button"> <a href="./about/" class="p-about__button-link c-button"> <span class="c-button__text">もっと見る</span> <span class="c-button__icon"><img src="./img/common/icon_arrow.svg" alt=""></span> </a></div>
.c-button { --_cols: minmax(0, 1fr) auto; --_icon-size: 25px; display: grid; grid-template-columns: var(--_cols); column-gap: 10px;}
.c-button__icon { order: var(--_icon-order); width: var(--_icon-size);}
.p-about__button-link { --_cols: auto minmax(0, 1fr); // grid-template-columnsを上書き --_icon-order: -1; // アイコンのorderを上書き --_icon-size: 15px; // アイコンのwidthを上書き}
この方法のメリットとデメリットは以下の通りです。
メリット | デメリット |
---|---|
|
|
JavaScriptでの関数に引数を渡す方法に近いかもしれません。
コンポーネント内で宣言するカスタムプロパティが10個も20個もあると大変ですが、4〜5個程度に収まるようであればコードの読みやすさも特に気にならないと感じます。
まとめ
「再利用性」「スタイルの意図」「独立性」を保つ必要があるコンポーネントの場合は、モディファイアでの対応が第一選択となります。
実務でよくある「コンポーネント設計が比較的緩めなデザインデータ」の場合には、「親要素からカスケーディングさせる方法」のいずれかを選択する必要があります。
個人的には基本モディファイアで対応し、パターンが多いコンポーネントでは「親要素からカスケーディングさせる方法」を選択するのがベターかなと感じております(「パターンが多いコンポーネント」をコンポーネント化する必要があるのかは議論の余地あり)。
その他にもユーティリティクラスで対応する方法などもありますが今回は割愛させていただきます。
冒頭でも述べましたが、CSS設計に正解はなく、そのプロジェクトにおける最適な方法を選び、プロジェクト全体を通して一貫した書き方を維持することが何よりも重要だと考えています。