最近便利なCSSおれおれAdvent Calendar 2023 – 01 日目

CSS 変数こと仕様名カスタムプロパティ custom properties です。

導入当初は割と言及されまくってたように思いますが広く普及した今日こんにちにおいてはなんかもう当たり前に利用されてて言及されない感じすらありますね。

自分も便利に使ってますが、そのひとつの例が UI コンポーネントのサイズ指定です。

ここでいうコンテキストは、同じ UI コンポーネントだけど利用場面に応じてちょっと変わるみたいなやつ、のその利用場面のことです。

利用場面に依るプロパティの指定

大小バリエーションを持つボタンを考えます。

HTML 構造はこんなんです。

<button class="Button size--medium">ボタン</button>
<button class="Button size--small">ボタン</button>

で、CSS はこんな感じです。(例なのでプロパティひとつだけにしました。)

.size--medium {
  --Button--font-size: 16px;
}

.size--small {
  --Button--font-size: 12px;
}

.Button {
  font-size: var(--Button--font-size);
}

.Button.size--medium と .Button.size--small それぞれに font-size を書いても良いのですが、変数にしておくことでここが変わるぞと見て分かるようになるかなと思います。

あと大きいのが、必ずしも .Button.size--medium である必要がないという点です。

CSS 変数の値を継承

ボタンの中にアイコンを置きたいですよね。置きましょう。

.SvgIcon というコンポーネントを用意しました。矩形の単色 SVG 画像です。

<button class="Button size--medium">
  <svg class="SvgIcon">…</svg>
  ボタン
</button>

<button class="Button size--small">
  <svg class="SvgIcon">…</svg>
  ボタン
</button>
.size--medium {
  --SvgIcon--size: 20px;
}
.size--small {
  --SvgIcon--size: 16px;
}

.SvgIcon {
  height: var(--SvgIcon--size);
  width: var(--SvgIcon--size);
}

先の .size--medium と .size--small が再登場しました。

これもボタンと同様 .SvgIcon.size--medium にしてそれぞれで px 指定しても良い……は良いのですが、その場合はアイコンのタグの方にもサイズ指定のクラス名が必要です。しかし CSS 変数だけ設定するようにすることで、必ずしもその要素で指定する必要はなく、継承できる上位要素でも同じ効果を得ることができます。

「コンテキスト」という表現を用いたのはそのためです。コンポーネント本体ではなくどこの要素内に配置されるかで挙動(描画)が変わってきます。

ボタンの中ではなく逆にボタンを含む複合コンポーネントでも同じようなことができますね。その場合は上位要素でサイズを指定します。

<form class="SearchForm size--medium">
  <input />
  <button class="Button">
    <svg class="SvgIcon">…</svg>
    検索
  </button>
</form>

変数の設定はコンポーネントごとに

ここでいうコンポーネントは .Button とか .SvgIcon を指します。

このやり方をあらゆるコンポーネントへ適用することができるので、.size--medium が持つ変数もそれに応じて多くなります。しかし巨大な .size--medium の定義をするのは可読性が低くなるでしょう。それは巨大だからではなく利用箇所と離れているためです。

.size--medium {
  --Button--font-size: 16px;
  --SvgIcon--size: 20px;
  /* …ここに100行くらい */
}

.size--small {
  --Button--font-size: 12px;
  --SvgIcon--size: 16px;
  /* …ここに100行くらい */
}

/* …ここに1000行くらい */

.Button { … }

/* …ここに1000行くらい */

.SvgIcon { … }

コードあちこち飛び回りたくないですよね。

ひとつの大きな .size--medium を作るより、.Button や .SvgIcon のような利用箇所ごとに何度でも .size--medium の宣言を書くことで、ボタン用の CSS 設定、SVG アイコン用の CSS 設定、といったグループになります。

.Button { … }

.size--medium {
  --Button--font-size: 16px;
}

.size--small {
  --Button--font-size: 12px;
}

/* …ここに1000行くらい */

.SvgIcon { … }

.size--medium {
  --SvgIcon--size: 20px;
}

.size--small {
  --SvgIcon--size: 16px;
}

クラス名や変数名

今回はサイズは size--<サイズ名> 、CSS 変数は --<コンポーネント名>--<プロパティ名> みたいにしました。

被らなければ何でも良いかなと思いますが、被らないようルールを定めておくことは大事だと思います。複数名でやる場合は特にそうですが、個人でも昨日の自分は他人ですから、まあなんか言語化しておくと良いでしょう。

利用可能な文字

ちなみに CSS 変数(カスタムプロパティ)の名前は大文字小文字は区別されます。--ginpei と --Ginpei は別人です。

おしまい

例はサイズでやったけど色とかのテーマ的な用途も同様にやれるかなと思います。

あと var() に初期値を与えた方が丁寧かなという気もするけれど利用箇所大杉さんなのでない方がおすすめです。それよりはわかりやすく失敗してる方がすぐ気付いて修正できるからいいかなと思います。

ちなみに、上位要素のプロパティ指定が下位要素でも利用されるやつ(color とか)が継承 inherit で、複数の宣言のうちセレクターの優先度で適用プロパティが決まるのがカスケード cascade ですね。ですよね?

参考