CSS
Sass
CSS設計

テーマ切り替え可能なCSS設計について考えてみた

最近、1プロジェクトの中に複数サービスがあり、サービスによってコンポーネントの色やサイズを調整したいという要望がありました。
このとき、CSS設計をどのように行うべきかを考えた結果のまとめになります。

注意

他によい方法があるかもしれませんので、検討時に選択肢の1つとして考えていただければと思います。
また、よりよい方法を知っているよという場合、是非その方法を教えていただけるとうれしいです。

実装について

わかりやすいのでボタンで考えてみます。

サービスのテーマカラーのボタンを設置したいとします。
このとき、btn--serviceをクラスに指定すれば、指定した色のボタンが実装できるとします。
そうなると下記のようなコードになります。

.btn {
  ...

  &--service {
    background-color: #f00;
  }
}
<button class="btn btn--service">テキスト</button>

よく見慣れた形です。

複数サービスの場合

複数のサービスがあった場合、どうなるでしょうか。
すぐに思いつくのが、それぞれ別のクラス(modifire)を作成する方法でしょうか?

.btn {
  ...

  &--serviceA {
    background-color: #f00;
  }
  &--serviceB {
    background-color: #0f0;
  }
}
<button class="btn btn--serviceA">テキスト</button>
<button class="btn btn--serviceB">テキスト</button>

このような形になるかと思います。
(※本来であれば、サイズ用のクラスなどがあるかと思いますが、ここでは省略しています)

では、次は問い合わせボタンも定義していくとします。
その際、問い合わせボタンの色は、サービスごとで違う色にしたいとします。
先ほどと同じように対応するのであれば、下記のようになるかと思います。

.btn {
  ...

  &--inquiryServiceA {
    background-color: #f00;
  }
  &--inquiryServiceB {
    background-color: #0f0;
  }
}
<button class="btn btn--inquiryServiceA">テキスト</button>
<button class="btn btn--inquiryServiceB">テキスト</button>

これでも問題はないのですが、そのサービスの中で使用するのに、わざわざそのサービス名がついたクラス名をつけるのって面倒ではないでしょうか?

scssを別にする

では問題を解決するために、scssを別にしてみます。

serviceA/_btn.scss

.btn {
  ...

  &--inquiry {
    background-color: #f00;
  }
}

serviceB/_btn.scss

.btn {
  ...

  &--inquiry {
    background-color: #0f0;
  }
}

こうなれば、これをそれぞれのサービスの共通scssでimportすればいいだけです。

でも、ボタンコンポーネントがサービスごとに散ってしまいました。
やはりボタンはボタンで固まっていて欲しくないでしょうか?

mixinという方法もありだとは思いますが、毎回作成するのも少し面倒なので、別の方法を考えてみました。

そこで、提案するのが、色などを管理するtheme.scssをサービスごとに作成し、ここで管理するという方法です。

対応方法

_btn.scss

.btn {
  ...

  &--inquiry {
    background-color: $inquiry_color;
  }
}

background-colorの指定を変数にしました。
ここで宣言した変数を、各サービスで上書きしていくことになります。

base/_theme.scss

// 指定がなかった場合のデフォルトを指定しておく
$inquiry_color: #fff !default

デフォルトとなる色などを指定しておきます。
今は必要ないかもしれないですが、今後上書きの必要がない場合に使用していきます。

serviceA/_theme.scss

$inquiry_color: #f00;

serviceA用の色を指定します。

serviceB/_theme.scss

$inquiry_color: #0f0;

serviceB用の色を指定します。

serviceB/layout.scss

@import "base/theme";
@import "serviceA/theme";
@import "btn";

importの順番が重要になります。
まず、デフォルトのテーマを読みこんで、その後にサービスAのテーマを読み込んでいます。
その結果、base/themeで指定されたものをベースにし、その後serviceA/themeで指定されたもので上書きすることになります。

※パスは必要に応じて修正してください

serviceB/layout.scss

@import "base/theme";
@import "serviceB/theme";
@import "btn";

こちらに関してはserviceAと同様になります。

まとめ

以上が対応方法になります。

このようにサービスごとに変化させたい値を変数で切り出し、読み込み時に上書きしていくことで、コンポーネントをまとめたままでも、サービスごとのModifire(パターン違いのクラス)を定義する必要がなくなります。

もちろん、普通にModifireとして定義した方がいい場合もあるので、常にこの方法の方がいいということはないと思います。
状況に応じて、このようなCSS設計も選択肢の1つに加えていただければと思います。