CSS の @property ルールは、明示的に CSS のカスタムプロパティ(CSS 変数)の構文や継承、初期値を定義できる仕組みで、2024 年 7 月 9 日リリースの Firefox 128 でサポートされたことで、主要ブラウザでのサポートが出揃いました1

@property を使用する目的として、大きく以下の 2 つに分けられます。

  1. カスタムプロパティの定義
  2. カスタムプロパティのアニメーション

この記事では、上記の 2 点を軸に、@property の基本的な使い方、さらに @property の課題について説明していきます。

本記事で紹介する、CSS の @property ルールや、JS の registerProperty() メソッドは、W3C の仕様では「CSS Properties and Values API Level 1」に属しています。

主要ブラウザではサポートされましたが、現時点では草案(Working Draft)段階のため、今後、実装方法や機能に変更が発生する可能性がある点には注意が必要です。

カスタムプロパティの定義

見出し「カスタムプロパティの定義」

@property を使用することで、カスタムプロパティの型の指定、デフォルト値の指定、継承の有無を定義することが可能になります。

以下は @property ルールの基本的な構文です。

@property の構文
@property --color-accent {
  syntax: "<color>";
  inherits: true;
  initial-value: hsl(180 100% 23%);
}

ここでは、カスタムプロパティ --color-accent に対して以下の定義をしています。

  • CSS のデータ型は <color>
  • 継承する(true
  • 初期値は hsl(180 100% 23%)

後述しますが、ここで定義したルールは、グローバルスコープになる点を覚えておきましょう。

定義したカスタムプロパティは、これまでと同じように使用します。もし、指定した値の型が syntax で指定した型と異なる場合には initial-value が適用されます。

定義したカスタムプロパティを使用する
@property --color-accent {
  syntax: "<color>";
  inherits: true;
  initial-value: hsl(180 100% 23%);
}

/* ✅ 型が一致 */
.foo {
  --color-accent: hsl(180 17% 84%);
  color: var(--color-accent);
}

/* 🚫 型が異なるので `initial-value` が適用される */
.foo {
  --color-accent: 100;
  color: var(--color-accent); /* hsl(180 100% 23%) */
}

なお、@property ルールにおいて、syntaxinitial-value の型が異なる場合には、定義自体が無効になります。

syntax と initial-value の型が異なる例
/* 🚫 `syntax` と `initial-value` の型が異なるので無効 */
@property --size {
  syntax: "<length>";
  inherits: false;
  initial-value: 100%;
}

.foo {
  --size: 100px;
}
.foo > .bar {
  /* `@property` は無効になるが、カスタムプロパティは有効 */
  inline-size: var(--size); /* 100px */
}

上記の例では、syntax<length> ですが、initial-value の値は 100% なので、型は <percentage> もしくは <length-percentage> である必要があります。

このとき、@property ルールのみが無効になり、カスタムプロパティ自体は有効になります。一見すると正しく動いており、見落としがちなので注意が必要です。

例えば、syntax の値にスペルミスがあったとしても、@property ルールが無効になるだけで、デベロッパーツールで確認しなければ気づかない可能性があります。


これらの点を踏まえて、syntaxinheritsinitial-value の順番で、より詳しく見ていきましょう。

syntax では、<length> のような CSS のデータ型か、auto のようなキーワード値を指定します。なお、値は引用符で囲う必要があります。

syntax は必須なので、指定がないと @property ルールが無効になります。

syntax の例
/* データ型を指定 */
@property --foo {
  syntax: "<length>";
  /* ... */
}

/* キーワード値を指定 */
@property --bar {
  syntax: "auto";
  /* ... */
}

論理和(OR)は |(VERTICAL LINE)を使用します。

以下のように、複数の型やキーワード値のいずれかに一致するように条件づけできます。

syntax に論理和(OR)を指定する例
@property --foo {
  syntax: "<number> | <angle>";
  /* ... */
}
@property --bar {
  syntax: "<length> | auto";
  /* ... */
}
@property --baz {
  syntax: "start | center | end";
  /* ... */
}

margin-block: 20px 40px のように、値を複数指定する規則には、データ型の直後に +(PLUS SIGN)や #(NUMBER SIGN)を使用します。

量指定子区切り
+スペース区切り
#カンマ区切り
syntax にリストを指定する例
/* <length> のスペース区切り */
@property --margin-block {
  syntax: "<length>+";
  /* ... */
}

/* <number> のカンマ区切り */
@property --matrix-number {
  syntax: "<number>#";
  /* ... */
}

.foo {
  --margin-block: 20px 40px;
  --matrix-number: -1, 1, -1, -1, 0, 0;

  margin-block: var(--margin-block);
  transform: matrix(var(--matrix-number));
}

syntax に指定できるデータ型は、以下のとおりです(2024 年 7 月時点)。

数が多いので圧倒されますが、最初からすべてを覚える必要はなく、必要に応じて MDN のページなどを参照するとよいでしょう。

データ型内容値の例
<length>長さ100px1em50cqi
<percentage>パーセント値50%-10%
<length-percentage>長さ、パーセント値100px1em50cqi50%
<number>整数、小数点、指数表記101.6-501e2
<integer>整数10-50
<color>カラーblue#c0ffee
oklch(40% 0.037 195)
<image>二次元画像url('bg.jpg')
linear-gradient(blue, white)
<url>url() 関数url('bg.jpg')
<angle>角度90degcalc(pi / 2 * 1rad)
<time>時間0.2s400ms
<resolution>解像度300dpi3dppx
<transform-function>座標変換関数translateX(100px)
perspective(200cqi)
<transform-list>座標変換関数のリストtranslateX(100px) rotate(90deg) scale(1.2)
<custom-ident>ユーザ定義の識別子fadedisc

以下、使い方に気をつけたいデータ型をいくつかピックアップします。

<length-percentage>

この見出しのリンク

まず、<length-percentage> は、一見すると <length> | <percentage> と同じようですが、calc() 関数を使用したときに違いがあります。

例えば、calc(100% - 40px) は、<length-percentage> では有効ですが、 <length> | <percentage> では無効になります。

"<length-percentage>" と "<length> | <percentage>" の比較
@property --length-percentage {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --length-or-percentage {
  syntax: "<length> | <percentage>";
  inherits: false;
  initial-value: 100%;
}

.foo {
  /* ✅ 有効 */
  --length-percentage: 400px;
  --length-percentage: 50%;

  --length-or-percentage: 400px;
  --length-or-percentage: 50%;
}
.bar {
  /* ✅ 有効 */
  --length-percentage: calc(100% - 40px);

  /* 🚫 無効 */
  --length-or-percentage: calc(100% - 40px);
}

また、40px 10% のように、長さとパーセント値が混在するときにも、<length-percentage>+ を使用する必要があります。

@property --length-percentage {
  syntax: "<length-percentage>+";
  inherits: false;
  initial-value: 10% 10%;
}
@property --length-or-percentage {
  syntax: "<length>+ | <percentage>+";
  inherits: false;
  initial-value: 10% 10%;
}

.foo {
  /* ✅ 有効 */
  --length-percentage: 40px 10%;

  /* 🚫 無効 */
  --length-or-percentage: 40px 10%;
}

<number><integer>

この見出しのリンク

<number><integer> は似ていますが、<integer> では整数のみを受け入れるので、小数点や指数表記は無効になります。

"<number>" と "<integer>" の比較
@property --number {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
@property --integer {
  syntax: "<integer>";
  inherits: false;
  initial-value: 0;
}

.foo {
  /* ✅ 有効 */
  --number: 1;
  --number: -1;
  --number: 3.14;
  --number: 3.0;
  --number: 1e2;
}
.bar {
  /* ✅ 有効 */
  --integer: 1;
  --integer: -1;

  /* 🚫 無効 */
  --integer: 3.14;
  --integer: 3.0;
  --integer: 1e2;
}

<custom-ident> は、例えば animation-name: fade における fade のように、ユーザが定義する識別子や、list-style-type に指定する discdecimal のように、ブラウザで定義されている識別子を指します。

なお、initial-valueinitialinheritunsetrevertrevert-layer といった CSS 全体のキーワード(CSS-wide keywords)を指定すると無効になります。

ただ、それ以外のキーワードであれば指定できるので、例えば、本来であれば <color> に属するカラーネームの blue を指定しても有効です。

"<custom-ident>" の initial-value にカラーネームを指定する例
/* 非推奨 */
@property --custom-color {
  syntax: "<custom-ident>";
  inherits: false;
  initial-value: blue;
}
.foo {
  color: var(--custom-color); /* blue */
}

これは、animation-name: blue の指定が有効なことからも理解できます。

上記のような使い方は避けるべきだと考えますが、このような <custom-ident> の型の特性を認識しておいたほうがよいでしょう。

全称構文定義(*

見出し「全称構文定義(」

「全称構文定義」というと仰々しく感じられますが、英語表記では universal syntax definition で、すべての型を受け入れる構文定義を意味します。

CSS の全称セレクタのように *(ASTERISK)を使用しますが、データ型同様に引用符で囲う必要があります。

全称構文定義の例
@property --any {
  syntax: "*";
  inherits: false;
}

なお、全称構文定義のときのみ initial-value を省略することができます。

カスタムプロパティが評価されるタイミング

見出し「カスタムプロパティが評価されるタイミング」

前回の記事「CSS の値の処理を探究する」で説明しましたが、カスタムプロパティが評価されるのは Computed Value のタイミングです。

そのため、カスタムプロパティの値に var() 関数や calc() 関数を指定した場合にも、このタイミングで参照先の値に置き換わります。

以下の例では、Computed Value 時点での --size の値は calc(100% - 40px * 2) となり、<length-percentage> と型が一致するので有効な値です。

@property --size {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 100%;
}

.foo {
  --size-1: 100%;
  --size-2: 40px;
  --size-3: calc(var(--size-1) - var(--size-2) * 2);
  --size: var(--size-3);

  inline-size: var(--size);
}

カスタムプロパティのフォールバック

見出し「カスタムプロパティのフォールバック」

@property ルールの syntax で指定した型と、カスタムプロパティの値の型が一致しないときには、initial-value で指定した値が適用されます。

カスケードのステップをさかのぼることも、var() 関数の第二引数に指定された代替値が使用されることもありません。

型が一致しないコードの例
@property --size {
  syntax: "<length>";
  inherits: false;
  initial-value: 100px;
}

.foo {
  /* 型が異なる値を指定(<length-percentage>) */
  --size: calc(100% - 40px * 2);

  /* カスケードの優先度が低い指定 */
  inline-size: 400px;

  /* 代替値 `50em` を指定 */
  inline-size: var(--size, 50em);
}

この例では、inline-size の値に指定した --size の型が異なるため無効になり、initial-value で指定した 100px が適用されます。

もし、この状態で @property の定義自体がない場合には、IACVT と呼ばれる状態になります。その場合でも代替値が使われることはなく、inline-size: unset が指定されたのと同じ結果になります。

var() 関数の第二引数に指定した代替値は、@property の定義がなく、カスタムプロパティ(--size)の定義もない場合、もしくは、--size に明示的に initial を指定した場合に使用されます2

inherits では、カスタムプロパティの継承を許可するかを指定します。なお、未定義のカスタムプロパティのデフォルト値は true です。

@property において inherits の指定も必須で、指定がないとルールが無効になります3

inherits: false を指定した場合には、カスタムプロパティを使用できるのは対象の要素のみとなり、子孫要素では使用できません。

inherits に false を指定する例
@property --color-accent {
  syntax: "<color>";
  inherits: false;
  initial-value: blue;
}

/* 親要素 */
.foo {
  --color-accent: teal;
  color: var(--color-accent);
}
/* 子要素 */
.foo > .bar {
  color: var(--color-accent);
}

上記のコードでは、カスタムプロパティ --color-accent は親要素の .foo で指定していますが、inherits: false を指定しているので、子要素 .barcolor プロパティに対して teal は適用されずに、initial-value で指定した blue が適用されます。

指定自体が無効になるのではなく、initial-value が適用される点を押さえておきましょう。

initial-value では、カスタムプロパティの初期値を指定します。

前述したように、syntax で指定した型と、initial-value で指定した値の型が異なる場合には、@property の定義が無効になる点に注意が必要です。

この initial-value は、以下の場合にフォールバックとしての役割を果たします。

  • カスタムプロパティを宣言せずに var() 関数で対象のプロパティを使用した場合
  • @property で指定した型と、カスタムプロパティの値の型が異なる場合
  • @propertyinherits: false を指定し、カスタムプロパティを子孫要素で使用した場合

以下にコード例を掲載します。

initial-value が適用される例
@property --color-accent {
  syntax: "<color>";
  inherits: false;
  initial-value: blue;
}

/* カスタムプロパティを宣言せずに `var()` を使用 */
.foo {
  color: var(--color-accent); /* blue */
}

/* `@property` で指定した型と、カスタムプロパティの値の型が異なる */
.bar {
  --color-accent: linear-gradient(teal, white);
  background: var(--color-accent); /* blue */
}

/* `@property` で `inherits: false` を指定し、カスタムプロパティを子孫要素で使用 */
.baz {
  --color-accent: teal;
}
.baz > p {
  color: var(--color-accent); /* blue */
}

仕様において、この initial-value は computationally independent(独立して計算可能)であることが条件とされています。

例えば、10px50% は独立して計算可能ですが、1em といった値はそうではないので、@property の定義自体が無効になってしまいます。

以下のように、一見問題のなさそうな指定でも、無効になってしまうので注意が必要です。

initial-value が無効になる例
@property --font-size {
  syntax: "<length>";
  inherits: false;
  initial-value: 1em;
}

ただ、この computationally independent の線引きは曖昧で、ブラウザごとの解釈にも相違があるようです。

例えば、2024 年 7 月現在では、initial-value: 50cqi は、Google Chrome や Safari では無効になりますが、Firefox では有効になります。

カスタムプロパティのアニメーション

見出し「カスタムプロパティのアニメーション」

@property のテクニックとしてよく取り上げられるのが、これまでは CSS では実現できなかったグラデーション(linear-gradient() など)のアニメーションが挙げられます。

これは、カスタムプロパティの値のみでは、ブラウザがどのように値の変化を補間すればよいかを知る方法がないためですが、@property でデータ型を明示することで、これらの値の補間が可能になりアニメーションが実現します。

以下はグラデーションにアニメーションを適用している 2 つのデモです。「PLAY」ボタンを選択すると、以下のアニメーションが開始します。

  1. background-image プロパティの linear-gradient() のアニメーション
  2. mask-image プロパティの radial-gradient() のアニメーション
Live Demo
Demo 1
Demo 2

linear-gradient() のアニメーション

この見出しのリンク

1 番目のデモの CSS を以下に抜粋します。

1 番目のデモの CSS(抜粋)
@property --color-stop-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: transparent;
}
@property --color-stop-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: transparent;
}

/* デモ 1 */
.demo-1 {
  background-image: linear-gradient(in oklch, var(--color-stop-1), var(--color-stop-2));
  animation: gradient 5s linear infinite;
}
@keyframes gradient {
  0% {
    --color-stop-1: oklch(74% 0.1 280);
    --color-stop-2: oklch(74% 0.1 60);
  }
  25% {
    --color-stop-1: oklch(74% 0.1 60);
    --color-stop-2: oklch(74% 0.1 60);
  }
  50% {
    --color-stop-1: oklch(74% 0.1 60);
    --color-stop-2: oklch(74% 0.1 280);
  }
  75% {
    --color-stop-1: oklch(74% 0.1 280);
    --color-stop-2: oklch(74% 0.1 280);
  }
  100% {
    --color-stop-1: oklch(74% 0.1 280);
    --color-stop-2: oklch(74% 0.1 60);
  }
}

このコードでは、background-image プロパティの linear-gradient() 関数に指定している 2 つのカラー、--color-stop-1--color-stop-2 の値をアニメーションさせています。

さて、上記のコードで、効率やメンテナンスの観点から改善できる箇所があります。

@keyframes で指定している oklch(74% 0.1 280)oklch(74% 0.1 60) の値は繰り返し出現するので、以下のようにカスタムプロパティに格納するのが望ましいです。

カラーの値をカスタムプロパティに格納した例
/* デモ 1 */
.demo-1 {
  --color-1: oklch(74% 0.1 280);
  --color-2: oklch(74% 0.1 60);

  background-image: linear-gradient(in oklch, var(--color-stop-1), var(--color-stop-2));
  animation: gradient 5s linear infinite;
}
@keyframes gradient {
  0% {
    --color-stop-1: var(--color-1);
    --color-stop-2: var(--color-2);
  }
  25% {
    --color-stop-1: var(--color-2);
    --color-stop-2: var(--color-2);
  }
  50% {
    --color-stop-1: var(--color-2);
    --color-stop-2: var(--color-1);
  }
  75% {
    --color-stop-1: var(--color-1);
    --color-stop-2: var(--color-1);
  }
  100% {
    --color-stop-1: var(--color-1);
    --color-stop-2: var(--color-2);
  }
}

しかし、2024 年 7 月現在では、このコードでは Firefox でアニメーションの補間が有効にならず、コマ送りになってしまうので、直接値を指定する必要があるようです。

ちなみに、この Firefox の現象は、transition にカスタムプロパティを指定しても発生しなかったので、@keyframes もしくは animation 特有のバグのようです。

radial-gradient() のアニメーション

この見出しのリンク

続いて、2 番目のデモの CSS を抜粋します。

2 番目のデモの CSS(抜粋)
/* デモ 2 */
@property --color-stop-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: transparent;
}
@property --color-stop-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: transparent;
}

.demo-2 {
  mask-image: radial-gradient(var(--color-stop-1), var(--color-stop-2));
  animation: mask 5s linear infinite alternate;
}
@keyframes mask {
  0% {
    --color-stop-1: transparent;
    --color-stop-2: transparent;
  }
  50% {
    --color-stop-1: black;
    --color-stop-2: transparent;
  }
  100% {
    --color-stop-1: black;
    --color-stop-2: black;
  }
}

ここでは、mask-image プロパティに対して radial-gradient() を使用して放射状のグラデーションを適用したマスクに対してアニメーションさせています。

conic-gradient() のアニメーション

この見出しのリンク

応用例として、conic-gradient() を使用することで、以下のような円グラフやプログレスバー風のアニメーションも実装できます。

Live Demo

ほかにもアイデア次第で、新たな表現が可能になりますが、カスタムプロパティのアニメーションを実装するときには、後述するアニメーション時のパフォーマンス にもご注意ください。

JavaScript で定義する方法

見出し「JavaScript で定義する方法」

JS でカスタムプロパティを定義するには、registerProperty() メソッドを使用します。

registerProperty() の例
CSS.registerProperty({
  name: '--prop-name',
  syntax: '<color>',
  inherits: false,
  initialValue: 'hsl(180 100% 23%)',
});

以下の差異はありますが、CSS の @property ルールと比較して、大きな違いはありません。

  • カスタムプロパティ名を name に指定する
  • syntax の指定は必須ではなく任意で、省略すると全称構文定義(*)になる
  • initialValue の表記はキャメルケース

@property の課題として、大きなところでは以下の 4 点が考えられます。

  1. ドキュメント内でグローバルスコープになる
  2. カスタムプロパティのアニメーションにおけるパフォーマンスの影響
  3. エディタのサポートが不十分
  4. ブラウザのサポートが不十分

順番に見ていきましょう。

まず、@property はドキュメント内でグローバルスコープになります。

もし、同じ名前のカスタムプロパティに対して、複数の @property ルールを定義した場合には、最後に定義したルールが有効になります。

@layer ブロックを使用することで、優先順位を変えることはできます。

このスコープの課題は Shadow DOM も例外ではありません。例えば、Shadow DOM の内側で registerProperty() メソッドを使用してカスタムプロパティを定義したとしても、Shadow DOM の境界を超えて共有されます。

以下は、Shadow DOM の内側で --color のルールを定義していますが、Shadow DOM の外側にある <p> 要素に対してもこのルールが適用されます。そのため、color プロパティには、initialValue で指定している blue が適用されます。

Shadow DOM の内側で registerProperty() を指定する例
<!-- HTML -->
<p>Lorem ipsum</p>
<my-shadowdom></my-shadowdom>

<!-- CSS -->
<style>
@property --color {
  syntax: "<color>";
  inherits: true;
  initial-value: teal;
}
p {
  color: var(--color); /* blue */
}
</style>

<!-- JS -->
<script>
class MyShadowDOM extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    const p = document.createElement('p');
    p.textContent = 'Shadow DOM';

    CSS.registerProperty({
      name: '--color',
      syntax: '<color>',
      inherits: true,
      initialValue: 'blue',
    });

    const style = document.createElement('style');
    style.textContent = `
      p {
        color: var(--color);
      }
    `;
    this.shadowRoot.append(style, p);
  }
}
customElements.define('my-shadowdom', MyShadowDOM);
</script>

もともと、カスタムプロパティ自体が Shadow DOM の境界を越えることができるのですが、W3C のドキュメントでは、以下のようにユニークな名前をつけて競合を回避すべきと提案されています。

If a custom property is intended for private internal usage for a component, however, it is recommended that the property be given a likely-unique name, to minimize the possibility of a clash with any other context.

CSS Properties and Values API Level 1 | W3C

いささか心もとないですが、カスタムプロパティの命名規則や設計について、再考の余地が残されていると考えられます。

アニメーション時のパフォーマンス

見出し「アニメーション時のパフォーマンス」

カスタムプロパティの値を transitionanimation でアニメーションさせると、再計算や再描画が繰り返され、パフォーマンスに悪影響を及ぼすことがあります。

W3C のドキュメントでは、以下の内容が該当するようです。

Like unregistered custom properties, the value of a registered custom property can be substituted into another value with the var() function. However, registered custom properties substitute as their computed value, rather than the original token sequence used to produce that value.

CSS Properties and Values API Level 1 | W3C

要約すると、未登録のカスタムプロパティ同様、@property で登録されたカスタムプロパティの値を、別の値に置き換えることが可能ですが、元の値をそのまま置き換えるのではなく、計算値(Computed Value)として置き換えられるとあります。

以下は、translate プロパティによって、要素が水平方向に移動するだけのアニメーションのコードです。次の 2 パターンでパフォーマンスの比較をします。

  • A. 直接 translate の値を指定したアニメーション
  • B. カスタムプロパティの値を指定したアニメーション
アニメーション時のパフォーマンスを比較するコードの抜粋
/* A. 直接、値を指定したアニメーション */
.a {
  animation: move-a 1s linear;
}
@keyframes move-a {
  from {
    translate: 0 0;
  }
  to {
    translate: 100% 0;
  }
}

/* B. カスタムプロパティのアニメーション */
@property --x {
  syntax: '<length-percentage>';
  inherits: false;
  initial-value: 0;
}
.b {
  translate: var(--x) 0;
  animation: move-b 1s linear;
}
@keyframes move-b {
  from {
    --x: 0;
  }
  to {
    --x: 100%;
  }
}

以下は、Google Chrome のデベロッパーツールの「Performance」タブで計測した結果のスクリーンショットです。

デベロッパーツールの「Performance」タブを表示している状態のスクリーンショット。A. 直接、値を指定したアニメーション、B. カスタムプロパティのアニメーションの比較

「B. カスタムプロパティのアニメーション」では、メインスレッドで多くの処理が発生しているのがわかります。アクティビティの詳細を見るとスタイルの再計算(Recalculate Style)が頻発しており、その結果として GPU を多く使用しているようです。

カスタムプロパティのアニメーションでしか実現できない表現もあるので、一概に否定するものではありませんが、メインスレッドの負担は INP(Interaction to Next Paint)にも影響するので、これらの点も考慮して実装するのがよいでしょう。

なお、このパフォーマンスの問題は、以下の Bramus 氏のブログ記事で知りました。

@property ルールを記述することで、使用するエディタによっては、後続するコードのシンタックスハイライトが壊れることがあります。

Visual Studio Code では、CSS ファイル(.css)は @property に対応しているようなのですが、ファイルの種類によっては影響を受けてしまいます。

例えば、このブログ記事は MDX 形式(.mdx)で管理しているのですが、コードブロックに @property ルールを含めると以下のようにシンタックスハイライトが変わってしまいます。

Visual Studio Code のスクリーンショット。`@property` ルールのコードを追加する前と追加した後を並べている
@prorperty を追加すると、後続するコードのシンタックスハイライトが変わる

この問題は、拡張機能に原因があると考えられそうですが、HTML ファイル(.html)内のスタイルシートで @property を定義したときも、同様に後続のコードに影響を及ぼします。エディタ全般において、まだサポートが十分ではないといえそうです4

前述の「カスタムプロパティのアニメーション」にて、Firefox でアニメーションの補間が適用されない例を取り上げました。

エディタ同様に、ブラウザにおける @property のサポートも安定しているとはいえないので、特にカスタムプロパティのアニメーションを実装したときには、各種ブラウザ検証は少し手厚くしたほうがよいかもしれません。

この記事では、@property の基本的な使い方や課題について説明しました。

カスタムプロパティを @property ルールで定義することで、より堅牢なコードになることに加え、カスタムプロパティのアニメーションが可能になり、CSS における表現の可能性が広がることが期待できます。

一方で、@property がグローバルスコープであるため、またしてもスコープの管理や命名規則に苦慮しなければならない点には、若干うんざりしてしまいます。

加えて、カスタムプロパティのアニメーションがパフォーマンスへの負担が大きいことを知ると、気軽に手を出しづらく感じてしまうのも事実です。

今回の考察を踏まえて、どのカスタムプロパティに対して @property を定義するのか、命名規則をどうするか、どこで定義するのかといった、設計面でのポイントを引き続き考えていきたいです。

本記事の作成にあたり、以下のウェブページを参考にしました。

脚注

  1. Safari での @property のサポートは 16.4 からです。

  2. この initial 値は「Guaranteed-Invalid Value」と呼ばれ、「Space Toggle」と呼ばれるテクニックで用いられることがあります。

  3. inherits を毎回指定するのは面倒なので、省略したら true でよいのでは、と思ってしまいますが、そういうわけにはいかないようです。

  4. さらにいえば、本記事に掲載しているコードブロックも、VS Code と同じエンジンの Shiki を使用しているため、@property ルールに後続するコードのシンタックスハイライトが一部壊れています。