Sassの代わりにネイティブCSSミックスインを使う方法
@mixinと@applyでSassミックスインをネイティブCSSへ移行する方法、引数の扱い、Sassが引き続き必要な機能を解説します。
ネイティブCSSミックスインとは、名前付きの宣言ブロックです。@mixin --nameで定義し、@apply --nameでスタイルルールに挿入します。Sassの@mixinと@includeに相当する機能を、ブラウザ側で実現するものです。
スタイルシートですでにカスタムプロパティとネイティブのネストを使っているなら、_mixins.scssパーシャルにあるいくつかの共有ミックスインだけが、Sassを使い続けている唯一の理由かもしれません。本記事では、実際のミックスインを1つネイティブCSSに移植し、2つのバージョンを並べて比較します。あわせて、移行できない種類のミックスインも紹介します。
重要なポイント
- ネイティブCSSミックスインでは、
@mixin --nameでブロックを定義し、@apply --nameでそれを使用します。名前はダッシュ付き識別子(dashed ident)で、@applyがSassの@includeの役割を果たします。 - Sassはコンパイル時に、ミックスインの宣言をインクルード先のすべてのルールにコピーします。ネイティブミックスインはブラウザが展開するため、配信されるスタイルシートに含まれる定義は1つだけです。
- MDNによると、CSSミックスインをデフォルトでサポートしているブラウザはまだなく、Baselineにも含まれていません。そのため、本番環境では引き続きSassを選択すべきです。
- Sassのマップ、
@eachや@forのループ、@if/@elseによる分岐、ビルド時の計算に依存するミックスインには、ネイティブの代替手段がありません。
既存のSassミックスイン
coverミックスインは、要素を位置指定された祖先要素の四辺すべてに固定します。最もシンプルな部類のミックスインなので、テストケースに適しています。ここでは2か所で使用しています。1つは背景(backdrop)、もう1つは疑似要素によるオーバーレイです。(Sass側の詳細については、SCSSミックスインのガイドを参照してください。)
@mixin cover {
position: absolute;
inset: 0;
}
.dialog-backdrop {
@include cover;
background: rgb(0 0 0 / 0.6);
}
.media-card.is-loading::after {
@include cover;
content: "";
background: rgb(255 255 255 / 0.7);
}
ミックスインの定義自体はCSSを一切出力しません。@includeのたびに、呼び出し元のルールへ宣言がコピーされます:
.dialog-backdrop {
position: absolute;
inset: 0;
background: rgb(0 0 0 / 0.6);
}
.media-card.is-loading::after {
position: absolute;
inset: 0;
content: "";
background: rgb(255 255 255 / 0.7);
}
ミックスインをインクルードするルールが増えるほど、出力に含まれるこの2つの宣言のコピーも増えていきます。
ネイティブCSSミックスインへの書き換え:並べて比較
宣言のみで構成されるSassミックスインをネイティブCSSに移植する作業は、機械的な3つの手順で完了します。名前の先頭に2つのダッシュを付けること、$パラメータを--パラメータに置き換えること、そして@includeを@applyに置き換えることです。
Sass
@mixin cover {
position: absolute;
inset: 0;
}
.dialog-backdrop {
@include cover;
background: rgb(0 0 0 / 0.6);
}
ネイティブCSS
@mixin --cover {
position: absolute;
inset: 0;
}
.dialog-backdrop {
@apply --cover;
background: rgb(0 0 0 / 0.6);
}
@applyが@includeの代わりとなり、ミックスイン名はダッシュ付き識別子でなければなりません。展開はブラウザが行うため、配信するスタイルシートに含まれる--coverの定義は1つだけです。本体は宣言のみで、@resultラッパーは不要です。これは、CSSWGがミックスインから@resultを削除することを決議したためです。ただし、ドラフト自体が「ミックスインはカスタム関数に比べて仕様がはるかに固まっていない」と注意喚起しており、構文は今後も変更される可能性があります。
CSSミックスインのブラウザサポート
ネイティブCSSミックスインは、現時点では本番環境で使用できません。MDNのカスタム関数とミックスインのガイドによると、ミックスインをデフォルトでサポートしているブラウザはなく、この機能はBaselineに含まれていません。Chromiumは他のエンジンに先行しており、CSSミックスインのIntent to Prototypeを提出済みですが、その取り組みはまだ実験段階です。
また、ネイティブCSSミックスインはプログレッシブエンハンスメントとしても機能しません。CSSのエラー処理では、パーサーが理解できない構文は破棄されます。そのため、未対応のブラウザは@applyを読み飛ばし、positionとinsetをミックスインからのみ受け取っている要素には、どちらも適用されません。本番環境ではSass版を維持し、ネイティブミックスインはSassコンパイラーを通さないプレーンな.cssファイルで試してください。
ミックスインのパラメータの移植
単純な値を渡すだけのSassミックスインのパラメータは、ネイティブCSSミックスインにそのまま移植できます。一方、ビルド時に値を計算したり、分岐やループを行ったりするミックスインは移植できません。以下は、insetを引数に取るcoverの例です:
// Sass
@mixin cover($inset: 0) {
position: absolute;
inset: $inset;
}
.frame::before { @include cover(0.5rem); content: ""; }
/* Native */
@mixin --cover(--inset) {
position: absolute;
inset: var(--inset);
}
.frame::before { @apply --cover(0.5rem); content: ""; }
各パラメータはミックスイン本体の中でプライベートなプロパティとなり、var()で読み取ります。ページ上の他のスタイルからは参照できません。var()のフォールバックは、他の宣言と同様に本体内でも機能します。CSS Custom Functions and Mixinsドラフトでは、パラメータに型を指定したり、デフォルト値を与えたりするための独自の構文も定義されています。現在の形式はドラフトで確認し、フォールバックがデフォルト値とまったく同じように動作するとは想定しないでください。2025年5月15日付のFirst Public Working Draftでは、パラメータのないミックスインにも括弧が必要でしたが、その後のドラフトでは省略できるようになりました。古いプロトタイプビルドでは@mixin --a()と@apply --a();の形式が必須だったため、両方の書き方を目にすることがあるでしょう。
以下のSassミックスインの機能には、ネイティブCSSでの代替手段がありません:
@eachと@forのループ@if/@elseによる分岐と@error- マップと、
sass:mapモジュールのmap.get math.divや単位変換などのビルド時の計算
ブレークポイント用のミックスインを見ると、移植できるものとできないものの境界がわかります:
@use "sass:map";
$breakpoints: (sm: 40rem, md: 64rem);
@mixin bp($name) {
@if not map.has-key($breakpoints, $name) {
@error "Unknown breakpoint: #{$name}";
}
@media (min-width: map.get($breakpoints, $name)) {
@content;
}
}
仕様では、Sassの@contentに相当するものとして@contentsが定義されています。そのため、原理的にはネイティブミックスインでも呼び出し元からブロックを受け取り、@mediaでラップできます。移植できないのは、マップの参照、@errorによる分岐、そしてメディアクエリ内の可変の幅です。
ネイティブCSSミックスインで何が得られるのか?
ネイティブCSSミックスインには、Sassミックスインと比べて2つのメリットがあります。ビルドステップが不要になること、そして引数がコンパイル時ではなくブラウザ内で解決されることです。1つ目については、ブラウザが@mixinと@applyを直接読み取るため、書いたCSSがそのまま配信されるCSSになります。
2つ目のメリットは、もう少しわかりにくいものです。Sassもvar()参照を出力できるため、実行時にカスタムプロパティに応じて変化する宣言をSassミックスインから出力することは可能です。しかし、Sassではコンパイル後に引数を変更できません。cover(0.5rem)は、ビルド時に一度だけinset: 0.5remへ変換されます。ネイティブミックスインはブラウザ内で解決され、仕様には、ミックスインが適用される要素に応じて値が変わる引数の扱いも含まれています。ただし、この挙動は仕様上のものであり、リリース済みのエンジンに実装されているわけではありません。なお、同じモジュールで定義されている兄弟機能であるカスタム関数(@functionで定義)は単一の値を返すのに対し、ミックスインは宣言のブロックを返します。
Sassを使い続けるべきなのはどんなときか?
cover、センタリング、visually-hiddenのような宣言のみのミックスインは、問題なく移植できます。セレクターを生成するもの、リストをループするもの、ビルド時に値を計算するものは移植できません。
| ミックスイン | そのまま移植できるか | 変更点 | 移植できない理由 |
|---|---|---|---|
| Cover / inset | はい | --付きの名前、@apply、var(--inset) | 該当なし |
| Visually-hidden | はい | --付きの名前、@apply | 該当なし |
| センタリング | はい | --付きの名前、@apply | 該当なし |
| ブレークポイント | いいえ | 原理的には@contentsによるブロックのラップが可能 | マップの参照、@error、メディアクエリの可変幅 |
ユーティリティクラスを生成するループ、マップで管理するデザイントークン、ブレークポイント用ヘルパーには、引き続きSassの@mixinと@includeを使用してください。また、ブラウザがミックスインをデフォルトでサポートするまでは、本番環境のスタイルはすべてSassで管理し続けてください。
まとめ
coverのような宣言のみのミックスインをネイティブ構文に置き換える作業は、手早く、ほぼ機械的に行えます。いつ切り替えるかはブラウザサポート次第ですが、そのサポートはまだ実現していません。実践的な次のステップとして、手持ちのミックスインを「宣言のみのミックスイン」と「Sassのロジックに依存するミックスイン」の2つのグループに分類しましょう。前者は別の.cssファイルに書き直し、実験的なChromiumビルドで試してみてください。MDNにデフォルトサポートが掲載されるまでは、Sass版はそのまま残しておきましょう。
よくある質問
ChromeでネイティブCSSミックスインをテストするにはどうすればよいですか?
ChromiumのミックスインのプロトタイプはCSSMixins機能フラグで制御されています。試すには、コマンドラインから--enable-features=CSSMixins引数を付けてChrome Canaryを起動してください。実装はまだ不完全で、CSSWGが仕様上の課題を解決するたびに変更されるため、一部の例が動作しなかったり、ドラフトとは異なる挙動を示したりする場合があります。実験や仕様へのフィードバックの目的にのみ使用し、本番環境のスタイルには決して使用しないでください。
メディアクエリ内でSassのブレークポイント変数をvar()に置き換えられないのはなぜですか?
var()関数はプロパティ値の中でしか機能せず、メディアクエリの条件はプロパティ値ではありません。カスタムプロパティはカスケードを通じて要素ごとに解決されますが、メディアクエリは特定の要素ではなく、ビューポートやデバイスに対して評価されます。そのため、min-width: var(--md)のような条件は無効であり、変数に格納したブレークポイントの幅をSassからネイティブのメディアクエリに移行することはできません。
ネイティブCSSミックスインにネストしたセレクターやメディアクエリを含めることはできますか?
仕様上は可能です。CSS Custom Functions and Mixinsドラフトでは、Sassミックスインと同様に、アンパサンドを使った::afterブロックのようなネストしたスタイルルールや、@mediaのような条件付きルールをミックスインの本体内に記述できます。ただし、ミックスインをデフォルトでサポートしているブラウザはまだないため、これは安定版のエンジンで動作する内容ではなく、仕様が意図している動作を説明したものです。
postcss-mixinsはネイティブCSSミックスインと同じものですか?
いいえ、異なります。postcss-mixinsプラグインでは、@define-mixinでミックスインを定義して@mixinで呼び出し、ドル記号付きのパラメータを使用して、ビルド時に展開します。一方、ネイティブCSSでは@mixinでミックスインを定義して@applyで呼び出し、ブラウザが展開します。@mixinキーワードは2つのシステムで正反対の意味を持つため、postcss-mixinsからネイティブ構文に移行するには、定義と呼び出し箇所の両方を書き換える必要があります。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k