CSSの@propertyルールでカスタムプロパティを定義する



CSSのカスタムプロパティ(CSS変数)は、変数という保存領域で値を管理して必要な時に利用したり効率よく値を変更するなど、メンテナンス性にとても優れた機能であります。

カスタムプロパティは手軽に変数で値を管理して利用していくことができますが、@propertyルールを利用することで、CSSのカスタムプロパティの定義において、プロパティの型や既定値の設定、プロパティが値を継承するかどうかなど、厳密な定義ができるようになります。

手軽に利用でいるカスタムプロパティは、誤って違う値を上書きしてデザイン崩れを起こしてしまうこともありますが、@propertyルールでカスタムプロパティの定義することで、CSSコーディングのメンテナンス性を高めることができます。

気になるWebブラウザのサポート状況ですが、主要ブラウザはすべてサポートされています。

Can I use (@property)
https://caniuse.com/mdn-css_at-rules_property


以下、@propertyルールによるカスタムプロパティの定義の基本構文になります。

@property --property-name {
  syntax: "<color>";
  inherits: false;
  initial-value: #ff0000;
}


上記の構文にある各設定については下記の内容となります。

–property-name
カスタムプロパティの名前

syntax
カスタムプロパティの型を設定

inherits
カスタムプロパティを継承するかどうかを設定

initial-value
カスタムプロパティの初期値を設定



@propertyルールでは、syntax,inherits,initial-valueの3つの設定を加えてカスタムプロパティを定義します。

@propertyでカスタムプロパティの定義の宣言をして、2つのハイフン(–)を前置きしたカスタムプロパティの名前を決めます。

syntaxでは、カスタムプロパティで扱える型を設定します。
以下、設定できる型になります。

<length>, <number>, <percentage>, <length-percentage>, <color>, <image>, <url>, <integer>, <angle>, <time>, <resolution>, <transform-function>, <custom-ident>


<length> は長さを表す値、<percentage> では割合を表す値、<color> であればカラーネームやカラーコードの値が扱えます。

inheritsでは、カスタムプロパティが子要素に継承されるかどうかを制御できます。
カスタムプロパティは値を上書きできるので、上書き後のカスタムプロパティに関連する子要素は値の変更の影響を受けます。親要素で設定された値を継承させる場合はtrue、継承させない場合はfalseを設定するなどして継承の制御ができます。

initial-valueでは、syntaxで設定したカスタムプロパティの型に合わせて初期値を設定します。

@propertyルールでカスタムプロパティを定義する


サンプルで確認しながら使い方を覚えていくといいでしょう。
以下、サンプルのベースのHTMLとCSSになります。

HTML

<main>

  <h2>CSS @property Rules</h2>

  <section class="element">
    <p>Lorem Ipsum</p>
  </section>

</main>



CSS

body {
  background-color: #eee;
}

h2 {
  width: calc(100% - 1.25rem);
  text-align: center;
  padding: 1.25rem 0;
}



それではまずは、背景色を扱うカスタムプロパティを定義して利用していく例を見てみます。
以下、CSSのサンプルコードになります。

CSS

@property --main-bgColor {
  syntax: "<color>";
  inherits: true;
  initial-value: lightgreen;
}

:root {
  --main-bgColor: #add8e6;
}

.element {
  max-width: 1000px;
  width: calc(100% - 1.25rem);
  padding: 1.25rem;
  min-height: 300px;
  margin-inline: auto;
  background-color: var(--main-bgColor);
}



syntaxでデータの型を <color> としていますので、カラーネームやカラーコードが指定できます。
inheritsの値を「true」とした場合は、親要素の上書きしたカスタムプロパティの値が子要素に継承されます。
initial-valueで定義した初期値はlightgreenですが、:rootで値を上書きしていますのでカスタムプロパティの値は #add8e6 となります。

@propertyルールでカスタムプロパティを定義(カスタムプロパティを継承)



inheritsの値を「false」とした場合は、カスタムプロパティを継承しないので、initial-valueの値の初期値がカスタムプロパティの値となります。

CSS

@property --main-bgColor {
  syntax: "<color>";
  inherits: false;
  initial-value: lightgreen;
}

:root {
  --main-bgColor: #add8e6;
}

.element {
  max-width: 1000px;
  width: calc(100% - 1.25rem);
  padding: 1.25rem;
  min-height: 300px;
  margin-inline: auto;
  background-color: var(--main-bgColor);
}


inheritsの値を「false」としていますので、:root での上書きは適用されず、elementの背景色はlightgreenとなります。
一度定義したらそこでしか変更できない定数のようなものです。

@propertyルールでカスタムプロパティを定義(カスタムプロパティを継承しない)



@propertyルールでカスタムプロパティを定義することで、扱えるデータの型の設定をはじめ、カスタムプロパティを継承するかどうか、初期値は何とするのかなど、カスタムプロパティを厳密に管理することができます。
これにより、誤って違う値を上書きしてしまうといったこともなくすことができます。

ほか、<percentage> の型であれば要素のサイズなどに使えるでしょう。

CSS

@property --item-size {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 94%;
}

main {
  --item-size: 100%;
}

.element {
  max-width: 1000px;
  /* width: var(--item-size); */
  width: calc(var(--item-size) - 1.25rem);
  min-height: 300px;
  margin-inline: auto;
}


inheritsをtrueとしておくと、コンテンツごとで横幅が調整しやすくなるでしょう。
また、calc関数もうまく活用してください。

#(カンマ区切り)と +(スペース区切り)の量化子


データの型の末尾に # を記述することで、カンマ区切りの値のリストを受け入れたり、+ を記述することでスペース区切りの値のリストを受け入れたりすることができます。

サンプルのHTML構造を少し変更し、テキストリンクを追加してみました。

HTML

<main>

  <h2>CSS @property Rules</h2>

  <section class="element">
    <p>Lorem Ipsum</p>
    <a href="#" class="textlink">Text Link</a>
  </section>

</main>



まずは # 記号を末尾に付けた例で見てみます。
色はRGBの光の三原色などを指定する場合、カンマ区切りで値を指定していきます。
syntaxは整数または小数部分のある数値を扱う <number> として、末尾に # 記号を追加します。
これでカンマ区切りのrgbの指定ができます。

CSS

@property --color-textlink {
  syntax: "<number>#";
  inherits: true;
  initial-value: 255, 0, 0;
}

.element {
  --color-textlink: 200, 0, 0;
}

.textlink {
  color: rgb(var(--color-textlink));
  text-decoration: none;
}



続いて、+ 記号を末尾に付けた例を見て見ます。
ショートハンドで記述できるものを含め、スペース区切りで値を指定するプロパティに対して使います。
paddingで内側の余白の調整する例で見ていきます。
syntaxは長さを表す値を扱う <length> として、末尾に + 記号を追加します。
これでスペース区切りで値が指定できます。

CSS

@property --padding-basic {
  syntax: "<length>+";
  inherits: false;
  initial-value: 16px 16px;
}

.element {
    padding: var(--padding-basic);
}

垂直線 (|) による論理和


垂直線 (|) は期待された条件の「論理和」を表し、長さを表す値またはパーセンテージでの割合の値のどちらか一方を受け入れます。

CSS

@property --padding-basic {
  syntax: "<length>+ | <percentage>+";
  inherits: true;
  initial-value: 16px;
}

main {
  --padding-basic: 20px 20px;
  /* --padding-basic: 1.25rem 1.25rem; */
  /* --padding-basic: 3% 3%; */
}

.element {
    padding: var(--padding-basic);
}


<length>はpxとremの値、<percentage>はパーセント(%)の値を受け入れることになります。

ただ、上記の場合ですと<length><percentage>のいずれかですので、どちらも含んだ値のリストは適用されません。
syntaxに <length-percentage> を設定することで、長さを表す値と割合の値のどちらも受け入れることができます。
(–padding-basic: 20px 3%;)

CSS

@property --padding-basic {
  syntax: "<length-percentage>+";
  inherits: true;
  initial-value: 16px;
}

main {
  /* --padding-basic: 20px 20px; */
  /* --padding-basic: 1.25rem 1.25rem; */
  /* --padding-basic: 3% 3%; */
  --padding-basic: 20px 3%;
}

.element {
    padding: var(--padding-basic);
}


これで1つの値のリストに px、rem、% の値を指定できます。

他にも、backgroundプロパティの指定でカスタムプロパティを使うのであれば、
syntax: "<color> | <url>#"」のような設定もできます。

*(アスタリスク)を使ったすべての型の指定


syntaxの設定を *(アスタリスク)とすることで、すべてのデータの型を適用することができます。
余白の指定でよくあります、長さやパーセントだけでなく、autoも含めたあらゆる指定に対応する場合に設定します。
以下はmarginプロパティの例になります。

CSS

@property --margin-basic {
  syntax: "*";
  inherits: true;
  initial-value: 16px;
}

main {
  --margin-basic: 20px auto;
}

h2 {
  margin: var(--margin-basic);
}


垂直線 (|) の論理和ではデータ型またはautoのような設定もできますが、どちらかとなるとプロパティに指定する値の幅が狭くなるので、プロパティによって多くの単位を扱う場合は、*(アスタリスク)ですべてのデータの型を扱えるようにしておく必要があるでしょう。

最後に


カスタムプロパティは、名前を決めて値を格納してと手軽に扱えるものですが、手軽に扱える分、誤って違う値を上書きしてしまい、レイアウト崩れといった問題が発生するリスクもあります。

@propertyルールでカスタムプロパティを定義することで、データの型から継承の有無、初期値など、カスタムプロパティを厳密に管理することができますので、複数箇所で利用して頻繁に変更される可能性があるカスタムプロパティは、@propertyルールを使って定義しておくといいでしょう。