タグアーカイブ カスタム関数

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の正式化を待つ必要がある
  • 循環依存は厳格に防止され、未対応ブラウザ向けのフォールバックが必須だ