タグアーカイブ スタイルシート

CSSの@functionルール入門。カスタム関数でスタイルを動的に管理

CSSの@functionルール入門。カスタム関数でスタイルを動的に管理

CSSに@functionというルールが導入されつつある。これはCSSで独自の関数を定義し、引数を受け取り、複雑な計算や条件分岐を経て値を返す仕組みだ。Sassの@mixinや@functionに似ているが、プリプロセッサを介さずにブラウザが直接解釈する点が新しい。

CSS-Tricksの記事によると、この機能は「CSS Custom Functions and Mixins Module Level 1」仕様の一部であり、カスタムプロパティ(変数)をさらに動的にした存在として位置づけられる。2026年6月現在は実験的機能だが、Chromeなど一部ブラウザでプレビューが始まっている。

本記事では@functionの基本構文、引数の扱い方、型チェック、カスケーディング、そして使用時の注意点までを整理する。

@functionとは何か、なぜ必要なのか

@functionとは何か、なぜ必要なのか

これまでCSSで動的な値を扱うにはカスタムプロパティ(--name)とcalc()の組み合わせが主だった。しかしcalc()だけでは条件分岐や複数ステップの演算が難しく、複雑な処理はSassに頼っていた。

@functionはこのギャップを埋める。CSSネイティブで「関数」を定義できるようになり、引数のバリデーション、デフォルト値、型チェック、メディアクエリに基づく分岐までを同一のスタイルシート内で完結させられる。

具体的には、色の透明度を動的に変えたり、画面幅に応じてフォントサイズを切り替えたり、複数の値をリストで受け取って最大値や最小値を計算したりといった処理が、プリプロセッサなしで可能になる。

従来のカスタムプロパティ(Before)
–base: 8px;
margin: calc(var(–base) * 2);
条件分岐や複数ステップの演算は不可
@functionルール(After)
@function –spacing(–n) { result: calc(8px * var(–n)); }
margin: –spacing(2);
引数と型チェック、デフォルト値、@media分岐も対応

このデモが示すように、@functionは単なる短縮記法ではなく、ブラウザのレンダリングエンジンが直接解釈するため、将来はパフォーマンス面でも恩恵が出ると期待されている。

基本構文を分解する

基本構文を分解する

関数の定義とresult記述子

@functionの基本形は以下のようになる。関数名はカスタムプロパティ同様、ダッシュ2つで始める必要がある。

@function --half(--size <length>) {
  result: calc(var(--size) / 2);
}

この例では--sizeという引数に<length>型を指定し、受け取った値を2で割った結果を返している。使用時は--half(20px)のように呼び出し、10pxが解決値となる。

result記述子は関数の戻り値を定義する必須要素だ。もしresultを書かないと、関数は常に「保証無効値(guaranteed invalid value)」を返し、実質的に機能しない。

returnsによる出力型の指定

returnsキーワードを使うと、関数が返す値の型を明示できる。これはバリデーションに役立つ。

@function --progression(--current <number>, --total <number>) returns <percentage> {
  result: calc(var(--current) / var(--total) * 100%);
}

上記では引数2つを数値型で受け取り、パーセンテージ型を返すことを宣言している。returnsを省略するとtype(*)相当となり、任意の型を受け入れる。

引数の型指定
–current <number>
数値のみ受け付ける。無効な値は関数呼び出し自体が無効化
戻り値の型指定
returns <percentage>
パーセンテージ以外が返るとブラウザが検知して無効化

こうした型チェックは大規模なコードベースでバグの早期発見に寄与する。JavaScriptのTypeScriptのように、CSSでも型安全性を確保できるわけだ。

複数型の許容とリスト引数

引数に複数の型を許容したい場合はtype()|を使う。

@function --transparent(--color <color>, --alpha type(<number> | <percentage>));

さらに、カンマ区切りで複数の値を1つの引数として受け取りたい場合、型の後ろに#を付与する。呼び出し側は波括弧{}で値を囲む。

@function --get-range(--list <length>#, --n <length>) {
  result: calc(max(var(--list)) - min(var(--list)) + var(--n));
}

div {
  padding-block: --get-range({10px, 100px, 50px, 25px}, 200px); /* 290px */
}

これはグラフのレンジ計算や、複数のスペーシング値から最適なマージンを導く際に便利だろう。

カスケードと条件分岐を活用する

カスケードと条件分岐を活用する

@functionのresultはCSSカスケードのルールに従う。つまり、複数のresultを記述した場合、最後にマッチした有効な値が採用される。

この性質を利用すれば、@media@container@supportsといった条件付きグループルールと組み合わせて、レスポンシブな値を関数内で完結できる。

@function --suitable-font-size() returns <length> {
  result: 16px;

  @media (width > 1000px) {
    result: 20px;
  }
}

body {
  font-size: --suitable-font-size();
}

この例では画面幅1000px以下なら16px、超えれば20pxを返す。従来はメディアクエリをスタイル宣言側で何度も書く必要があったが、関数に集約できるため保守性が向上する。

ただし、カスケードの「後勝ち」ルールには注意が必要だ。result: 16px;@mediaブロックより後に書くと、メディアクエリの結果に関わらず常に16pxが返る。記述順序を誤ると意図しない挙動になる。

ローカルスコープのカスタムプロパティ

@function内で定義したカスタムプロパティは、その関数内でのみ有効なローカルスコープとなる。グローバルに漏れ出さないため、命名の衝突を気にせず使える。

@function --spacing-scale(--multiplier) {
  --base-unit: 8px;
  result: calc(var(--base-unit) * var(--multiplier));
}

ここで定義された--base-unit--spacing-scaleの外部からは参照できない。大規模なCSS設計で名前空間の汚染を防ぐ手段として有効だ。

関数のネスト

ある@functionの中で別の@functionを呼び出すことも可能だ。これにより、単一責務の小さな関数を組み合わせて複雑な計算を構築できる。

@function --square(--n) {
  result: calc(var(--n) * var(--n));
}

@function --circle-area(--radius) {
  --pi: 3.14159;
  result: calc(var(--pi) * --square(var(--radius)));
}

.blob {
  width: calc(--circle-area(10) * 1px); /* 314.159px */
}
STEP 1 –circle-area が –radius を受け取る
STEP 2 内部で –square を呼び出して半径の2乗を計算
STEP 3 円周率を掛けて面積を返す

このネスト構造のおかげで、関数をレゴブロックのように組み立てられる。Sassで培われたモジュール設計の考え方を、そのままネイティブCSSに持ち込める未来が見える。

使用上の制約と注意点

使用上の制約と注意点

副作用の禁止

@functionは値を返すことだけが許されており、プロパティの変更や複数宣言の生成といった副作用は起こせない。これは関数型プログラミングの「純粋関数」に近い制約だ。

もし複数行のCSSプロパティをまとめて出力したい場合は、仕様策定中の@mixinルールを待つ必要がある。@functionと@mixinが揃えば、Sassの主要機能がCSSネイティブで再現されることになる。

循環依存の厳格な防止

CSSは循環参照に極めて厳しい。関数Aが関数Bを呼び、関数Bが関数Aを呼ぶような構造はブラウザが即座に検出し、両方の関数を無効化する。

これはカスタムプロパティの循環参照と同様の挙動だ。意図せず再帰を作り込まないよう、関数同士の呼び出し関係は明確に設計する必要がある。

循環依存の例
–func-a が –func-b を呼び出し
–func-b が –func-a を呼び出す
→ ブラウザが即座に両方を無効化
正しい依存関係
–func-a → –func-b → –func-c のように一方向の依存のみ
→ 安全に動作する

ブラウザサポートとプログレッシブエンハンスメント

2026年6月時点で@functionは実験的機能であり、未対応ブラウザでは単に無視される。そのため、フォールバックの記述が重要になる。

理想的には@supports (at-rule(@function))でサポートを判定したいが、この@supportsのat-rule評価機能自体がChrome 148以降でしか動作しないという皮肉な状況がある。

現実的な対策としては、従来のカスタムプロパティとcalc()で書いたスタイルをフォールバックとして先に宣言し、その下に@functionを使ったスタイルを記述する方法が考えられる。ブラウザが@functionを解釈できれば上書きされ、できなければ従来の記述が適用される。

/* フォールバック */
.container {
  margin-inline: 10px;
}

/* @function対応ブラウザでは上書き */
.container {
  margin-inline: --half(20px);
}

Sassの@functionとの違い

Sassの@functionとの違い

Sassにも同名の@functionがあるが、両者は動作の仕組みが根本的に異なる。Sassの@functionはコンパイル時にサーバーサイドで処理され、出力されるCSSには関数の痕跡が残らない。

一方、CSSネイティブの@functionはブラウザが実行時に解釈する。スタイルシート内に関数定義がそのまま存在し、ユーザーの画面幅やコンテナサイズに応じて動的に値が解決される。

この違いはパフォーマンス特性やデバッグのしやすさにも影響する。Sassの場合、コンパイル後のCSSしか確認できないが、ネイティブ@functionならブラウザのDevToolsで関数の解決過程を追えるようになる可能性がある。

Sassの@function
コンパイル時に処理され、静的な値としてCSSに出力される
実行時の再計算は不可、DevToolsで追跡困難
CSSネイティブの@function
ブラウザが実行時に解釈し、動的に値を解決する
メディアクエリに応じた再計算が可能、将来はDevTools対応も期待

なお、SassプロジェクトにCSSネイティブの@functionを混在させることは技術的には可能だが、同名の関数が競合する可能性があるため命名規則の整理が推奨される。

この記事のポイント

  • @functionはCSSネイティブでカスタム関数を定義するルールである
  • 引数、型チェック、デフォルト値、メディアクエリ分岐を1つの関数に集約できる
  • result記述子はカスケードに従い、複数記述時は後勝ちのルールが適用される
  • 副作用は禁止で、複数宣言の生成には@mixinの正式化を待つ必要がある
  • 循環依存は厳格に防止され、未対応ブラウザ向けのフォールバックが必須だ
CSSの!importantを使わずにスタイルを上書きする5つの方法

CSSの!importantを使わずにスタイルを上書きする5つの方法

CSSでスタイルが意図通りに適用されない時、!importantキーワードを使いたくなる。確かに即効性はあるが、乱用はカスケードを破壊し、保守性を著しく低下させる。

CSS-Tricksの記事では、!importantに依存しない複数の代替手法を紹介している。カスケードレイヤー、:is()擬似クラス、セレクタの重複、ソース順序の調整など、プロジェクトの規模や状況に応じた適切な方法が存在する。

この記事では、!importantがなぜ問題を引き起こすのか、そして具体的にどのような代替手段があるのかを実践的なデモを交えて解説する。

CSSの詳細度と!importantの問題点

CSSの詳細度と!importantの問題点

CSSの詳細度(Specificity)は、複数のスタイルルールが競合した時に、どちらを優先するかを決める重み付けの仕組みだ。基本的な優先順位は以下の通りとなる。

  • インラインスタイル(style="...")が最も強い
  • IDセレクタ(#header)はクラスやタイプセレクタより強い
  • クラス、属性、擬似クラスセレクタ(.btn[type="text"]:hover)は中程度
  • タイプセレクタと擬似要素(divp::before)が最も弱い

!importantはこの詳細度のルールを無視する。通常のカスケードの順序を飛び越えて、宣言を最優先させる強力なキーワードだ。

p {
  color: red !important;
}

#main p {
  color: blue;
}

この例では、#main pの方が詳細度が高いが、段落の文字色は赤になる。!importantが通常の詳細度計算を上回るからだ。

!importantが引き起こす負の連鎖

問題は!importantが一度使われると、連鎖的に増殖していく点にある。ある開発者がスタイルが効かない問題に直面し、!importantで強制適用する。後から別の開発者がそのコンポーネントを修正しようとするが、既存の!importantが邪魔をする。

この時、安全策としてさらに強い!importantを追加する選択が取られがちだ。なぜ最初の!importantが必要だったのか誰も把握していないため、取り除くリスクを避けるためである。この繰り返しでスタイルシートは制御不能な状態に陥る。

テーマ切り替えのような機能でこの問題が顕著になる。ダークテーマ用のスタイルが!importantによって上書きされず、UIが壊れるケースだ。

.button {
  color: red !important;
}

.dark .button {
  color: white;
}
通常テーマ

.buttonに!importantが付いているため、常に赤色。

ダークテーマ

.dark .buttonの白指定が効かず、赤のまま。

このデモは、!importantがあるとテーマクラスによる上書きが機能しないことを示している。

カスケードレイヤーによる体系的な優先度管理

カスケードレイヤーによる体系的な優先度管理

カスケードレイヤー(Cascade Layers)はCSSの比較的新しい機能で、スタイルの優先度をセレクタの詳細度ではなく、事前に定義した「層」で管理する。これにより、!importantに頼らずにスタイルの優先順位を制御できる。

まずレイヤーの順序を宣言する。下の例では、resetdefaultscomponentsutilitiesの4層を定義している。後に宣言されたレイヤーほど優先度が高くなる。

@layer reset, defaults, components, utilities;

次に、各レイヤーにスタイルを追加する。レイヤー間では宣言順が優先されるが、レイヤー内では通常通り詳細度が働く。

@layer defaults {
  a:any-link {
    color: maroon;
  }
}

@layer utilities {
  [data-color='brand'] {
    color: green;
  }
}

この例では、a:any-linkの方が[data-color='brand']より詳細度が高いが、utilitiesレイヤーがdefaultsレイヤーより後に宣言されているため、緑色が適用される。

サードパーティCSSの統合に効果的

カスケードレイヤーは外部フレームワークのCSSを統合する際に特に有効だ。フレームワークが高詳細度のセレクタを使っていても、レイヤーで包むことで自前のスタイルを優先させられる。

@layer framework, components;

@import url('framework.css') layer(framework);

@layer components {
  .card {
    padding: 2rem;
  }
}

フレームワークのスタイルをframeworkレイヤーに、自前のコンポーネントスタイルをcomponentsレイヤーに配置する。componentsレイヤーは後に宣言されているため、フレームワークのスタイルを詳細度に関係なく上書きできる。

!importantとカスケードレイヤーの意外な関係

興味深いことに、!importantをカスケードレイヤーと併用すると、レイヤーの優先順位が逆転する。通常のレイヤー順序が「utilities > components > defaults」だとすると、!importantを付けた宣言では「!important defaults > !important components > !important utilities」という逆順で評価される。

これは、下位レイヤーに属する必須スタイル(例えばアクセシビリティ関連)が、上位レイヤーの通常スタイルより優先されることを意味する。CSS-Tricksの著者Miriam Suzanne氏は、この挙動を「下位レイヤーが特定のスタイルを必須として主張する手段」と説明している。

:is()擬似クラスで詳細度を調整する

:is()擬似クラスで詳細度を調整する

:is()擬似クラスは、引数の中で最も詳細度の高いセレクタの詳細度を全体に適用する。これを使って、クラスセレクタの詳細度をIDセレクタレベルに引き上げることが可能だ。

例えば、サイトバー内のリンクにグレー色を指定する高詳細度のルールがあるとする。

#sidebar a {
  color: gray;
}

ナビゲーションリンクに青を指定したいが、単純なクラスセレクタでは詳細度が足りず上書きできない。この時!importantを使う代わりに、:is()で詳細度を上げる方法がある。

:is(#some_id, .nav-link) {
  color: blue;
}

:is()内の#some_idは実際の要素にマッチする必要はない。IDセレクタの詳細度を借用するためだけに使っている。これで.nav-linkセレクタがIDレベルの詳細度を得られる。

#sidebar a {
color: gray;
}

詳細度が高い#sidebar aが適用され、灰色になる。

:is(#some_id, .nav-link) {
color: blue;
}

:is()でIDレベルの詳細度を得て、青色を適用できる。

このデモは、:is()を使うことでクラスセレクタがIDセレクタレベルの詳細度を得られることを示している。

反対に、詳細度をゼロにしたい場合は:where()擬似クラスを使う。:where()は内包するセレクタの詳細度をすべて無視し、常に(0,0,0)として評価される。リセットスタイルやベーススタイルで、後から簡単に上書きできるようにしたい場合に有用だ。

シンプルな手法:セレクタの重複とソース順序

シンプルな手法:セレクタの重複とソース順序

セレクタを重複させる

クラスセレクタを重複させることで、詳細度を上げる最も単純な方法がある。.buttonの詳細度は(0,1,0)だが、.button.buttonと重ねると(0,2,0)になる。

.button {
  color: blue;
}

.button.button {
  color: red;  /* より高い詳細度 */
}

この手法は即効性があるが、多用するとコードの可読性が低下する。同じクラス名が繰り返されるため、意図がわかりにくくなるリスクがある。あくまで限定的な使用に留めるべきだ。

ソース順序を見直す

CSSは詳細度が同じ場合、後に宣言されたルールを優先する。この原則を利用すれば、!importantなしでスタイルを上書きできるケースが多い。

例えば、汎用的なルールが特定のコンポーネントスタイルを上書きしてしまう場合、両者の詳細度が同じなら、単にスタイルシート内の順序を入れ替えるだけで解決する。

/* 問題のある順序 */
.component {
  color: blue;
}

/* より汎用的なルールが後に来ると上書きされる */
a {
  color: red;
}

/* 解決策:順序を入れ替える */
a {
  color: red;
}

.component {
  color: blue;  /* これが適用される */
}

大規模なプロジェクトでは、スタイルシートの構成を最初から計画しておくことが重要だ。一般的なパターンは「リセット → ベーススタイル → レイアウト → コンポーネント → ユーティリティ」の順で、汎用性の高いものから特定のものへと進んでいく。

それでも!importantを使うべき正当なケース

それでも!importantを使うべき正当なケース

ここまで!importantの代替手法を紹介してきたが、すべてのケースで!importantが悪というわけではない。CSS-TricksのChris Coyier氏も別の記事で、!importantが正当な選択となる場面について論じている。

ユーティリティクラス

.visually-hiddenのようなユーティリティクラスは、その役割を確実に果たすために!importantが必要な場合がある。スクリーンリーダー向けに要素を視覚的に隠すこのクラスは、他のどのスタイルにも上書きされては困る。

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
}

同様に、.disabledのような状態クラスや、コンポーネントの基本スタイルにも!importantが適切な場合がある。これらのクラスは、その状態や役割を確実に表現する必要があるからだ。

サードパーティ製コードの上書き

編集できない外部ライブラリのスタイルを上書きする必要がある時、!importantは有効な手段となる。JavaScriptで動的に設定されるインラインスタイルを上書きする場合も同様だ。

アクセシビリティとユーザー設定

ユーザースタイルシート(視覚障害者などがブラウザに設定するカスタムスタイル)では、!importantが事実上唯一の確実な手段だ。ウェブページのスタイルがどのような詳細度を持つか予測できないため、ユーザーのアクセシビリティ設定を確実に反映させるには!importantが必要となる。

また、ユーザーのブラウザ設定を尊重するスタイルにも!importantが使われる。例えば、動きの削減を希望するユーザー向けの設定だ。

@media screen and (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

このメディアクエリは、ユーザーがシステム設定で動きの削減を有効にしている場合に、すべてのアニメーションとトランジションを実質的に無効化する。ユーザーのアクセシビリティ設定は常に最優先されるべきであるため、!importantの使用が正当化される。

この記事のポイント

  • !importantはカスケードの自然な流れを破壊し、スタイルシートの保守性を低下させる。特にチーム開発では負の連鎖を引き起こしやすい。
  • カスケードレイヤーを使えば、セレクタの詳細度に依存せず、レイヤーという抽象的なレベルでスタイルの優先度を管理できる。サードパーティCSSの統合に特に有効だ。
  • :is()擬似クラスは、引数内で最も高い詳細度を借用できる。クラスセレクタの詳細度をIDレベルに引き上げたい時に使える。
  • セレクタの重複やソース順序の調整はシンプルな解決策だが、可読性やメンテナンス性とのバランスを考慮する必要がある。
  • !importantにも正当な使用例がある。ユーティリティクラス、アクセシビリティ対応、ユーザー設定の尊重など、意図的にカスケードを無視すべきケースだ。