CSS変数:var()からテーマ、@propertyまで
CSS変数(カスタムプロパティ)ガイド
CSS変数:構文、スコープ、テーマ、JavaScript、@property | We.Inc
CSSカスタムプロパティを解説します。変数だけで作ったライトとダークのテーマをその場で確認でき、JavaScriptのgetPropertyValueとsetProperty、@property、Tailwind v4の@theme、つまずきやすい点も扱います。
CSS変数は、--brand: #2563eb; のように先頭にハイフンを2つ付けて宣言します。ページ全体から参照できるよう、たいていは:rootの中に書き、var(--brand)で読み出します。2つ目の引数は代替値で、var(--brand, blue)のように書きます。変数は色と同じように継承されるため、あるセクションで定義し直すと、その中のすべてに反映されます。そのおかげで、テーマを手軽に作れます。
構文の早見表
- グローバルに宣言する
- :root { --gap: 1rem; }
- 局所的に上書きする
- .card { --gap: 0.5rem; }
- JSで読み取る
- getComputedStyle(el).getPropertyValue('--gap')
- JSで書き込む
- el.style.setProperty('--gap', '2rem')
このガイドでは、宣言、スコープ、代替値の基本に続いて、変数だけを切り替えるテーマ切り替え、このページ上でJavaScriptから変数を読み書きできるツール、@propertyによる登録済みプロパティ、Tailwind v4がテーマを変数に変換する仕組み、そしてvar()が気付かないうちに効かなくなる間違いを取り上げます。
- テーマのデモを切り替えてみましょう。マークアップは一切変わらず、変わるのは6つの変数の値だけです。
- JavaScriptのツールで変数を選んで現在の値を読み取り、新しい値を設定して、サンプルが更新される様子を確認します。
- デザイントークン、代替値、@propertyのスニペットを下からコピーします。
- トークンには見た目ではなく役割で名前を付けましょう。ブランドカラーがオレンジに変わっても、--red-500より--color-dangerのほうが長く使えます。
- 2層構造にしましょう。生のパレットの値と、それを参照する意味的なトークンです。コンポーネントは意味的な層だけを使います。
- カスタムプロパティの名前は大文字と小文字を区別します。--Brandと--brandは別々の変数です。
- あなたのトークンを定義していないページにコンポーネントが置かれる可能性があるなら、代替値を使いましょう。
よくある質問
CSS変数とSassの変数の違いは何ですか?
Sassの変数はスタイルシートのコンパイル時に置き換えられ、出力には残りません。CSSのカスタムプロパティは実行時にブラウザーの中に存在し、DOMを通じて継承され、要素ごとに異なる値を持てます。さらに、ページの読み込み後にJavaScriptやメディアクエリのルールで変更することもできます。
メディアクエリの中でCSS変数を使えますか?
使えません。メディアクエリは継承元となる要素がない状態で評価されるため、@mediaルールの条件部分ではvar()を使えません。ただし、メディアクエリのブロックの中で変数の値を変更することはでき、これがトークンをレスポンシブにする一般的な方法です。
変数が効かないのはなぜですか?
綴りと大文字小文字、変数がその要素または祖先で定義されているか、最終的な値がそのプロパティにとって有効かを確認してください。置き換えた後の値が無効な場合、その宣言は計算値の時点で無効となり、プロパティは直前のルールではなく、継承値または初期値に戻ります。
CSS変数はアニメーションできますか?
登録されていない変数は文字列として扱われるため、なめらかに変化せず中間点で切り替わります。@propertyで<color>や<angle>などの構文を指定して登録すれば、トランジションやアニメーションでブラウザーがなめらかに補間できるようになります。
CSS変数はどこでも使えますか?
カスタムプロパティとvar()は、何年も前から主要なブラウザーすべてで動作しています。@propertyはそれより新しく、Firefoxが対応したのは2024年のバージョン128です。古いバージョンをサポートする場合はcaniuseで確認し、登録しない場合の代替手段も残しておきましょう。
スコープと継承
- カスタムプロパティは、ごく普通の継承されるプロパティです。:rootで定義すればすべての要素に届きます。.cardで定義すれば、そのカードと子孫だけに適用され、そこでは:rootの値より優先されます。より近い値が継承されるからです。
- これがコンポーネントのバリエーションを短く書ける理由です。ボタンはvar(--btn-bg)を読み取り、.btn-dangerは--btn-bg: var(--color-danger)を設定するだけです。状態ごとに別のルール一式を書く必要はありません。
計算値の時点で無効になる
- 値は置き換えられるまで分からないため、ブラウザーは解析の段階ではvar()を含む宣言をチェックできません。そのため、--size: redのときのmargin: var(--size)は、解析時には受け入れられ、あとになって初めて失敗します。
- 失敗すると、そのプロパティは未設定として扱われます。colorのように継承されるプロパティなら親の色になり、marginなら0になります。スタイルシートにある以前の有効なmarginのルールが復活することはありません。通常のカスケードの代替に慣れた人はここで驚きます。
デモで使っているテーマのトークン
ライトとダークの値
スコープと代替値をその場で確認
局所的な上書きと代替値
2枚目のカードは、自分用に--accentを定義し直しています。3枚目は誰も定義していない変数を使っているため、代替の色が表示されます。
変数だけで作るライトとダークのテーマ
コンポーネントのルールは1組だけです。テーマとは値の組み合わせの違いであり、システムの設定かdata-theme属性によって選ばれます。
JavaScriptで変数を読み書きする
getPropertyValueとsetProperty
どの要素からでも計算値を読み取れます。全体を変えるなら:rootに、その要素の配下だけに効かせたいならその要素に書き込みます。
@property:型付きでアニメーションできる変数
グラデーションの角度をアニメーションさせる
--angleを<angle>として登録すると、ブラウザーが値を補間できるようになります。登録しないと、グラデーションは回転せずに飛び飛びに切り替わります。
Tailwind v4の@theme
トークンがユーティリティと変数になる
Tailwind v4では、@themeの中の変数からbg-brandのようなユーティリティが生成され、さらにvar()で使える本物のCSS変数としても出力されます。
つまずきやすい点
よくあるつまずき
- @mediaの条件にvar()を使う
- メディアの条件は要素ごとに評価されない
- 代わりにメディアクエリのブロック内で変数を変更する
- 単位付きの値が効かない
- --n: 4; のあとwidth: var(--n)pxと書くと'4 px'になる
- calc(var(--n) * 1px)を使う
- プロパティが初期値に戻る
- 計算値の時点で無効になっている
- 置き換え後の値を確認し、代替値を追加する
- url()の中で変数が無視される
- url(var(--x))は使えない
- url(...)全体を変数に入れる
- 派生した変数が上書きを無視する
- :root { --big: calc(var(--base) * 2) } は:rootで解決されるため、.card { --base: 5px } としても--bigは変わらない
- .cardでも--bigを宣言し直すか、使う場所で計算する
変数だけで作るライトとダークのテーマ
テーマ
ライトモード
ダークモード
週次レポート
先週より注文が増加
件のサンプル注文
詳細を見る
変わるのはここだけ
CSSをコピー
JavaScriptから変数を読み書きする
下のサンプルのボックスは、枠線にvar(--demo-accent, #2563eb)、角にvar(--demo-radius, 8px)を使っています。どちらかの変数を設定すると、ボックスが変化します。
変数名
新しい値
カスタムプロパティの名前は、--demo-radiusのようにハイフン2つで始まります。
読み取る
設定
削除
--demo-radiusを試す
サンプルのボックス
(未設定)
無料で始める · クレジットカード不要
関連: ガイド|Webマーケティングと集客を学ぶ
製品
ご利用いただける方
機能
リソース
会社情報
サイトマップを見る