`@variant`でコンパウンド・スタックバリアントが使えるように
@variantディレクティブがコンマ区切りのコンパウンドバリアントとコロン区切りのスタックバリアントをサポートし、複数バリアントの指定を簡潔に記述できるようになりました。
背景
@variantディレクティブは当初、シンプルさを優先して単一バリアントのみをサポートする設計で導入されました。複数のバリアントに同じスタイルを適用したい場合、開発者はルールの重複記述や@variantのネストを強いられていました。たとえばhoverとfocusの両方に同じスタイルを適用するには、同一ルールを2回書くか、ネストを深くする必要がありました。
コミュニティからの要望(#19526、#19884)を受け、機能の必要性が確認できたタイミングで本PR(#19996)がこれらを統合・発展させる形で実装されました。
技術的な変更
packages/tailwindcss/src/variants.tsの substituteAtVariant 関数が拡張され、バリアントパラメータのコンマ区切り・コロン区切りをパースして展開する処理が追加されました。
変更前:
// Starting with the `&` rule node
let node = styleRule('&', variantNode.nodes)
let variant = variantNode.params
let variantAst = designSystem.parseVariant(variant)
if (variantAst === null) {
throw new Error(`Cannot use \`@variant\` with unknown variant: ${variant}`)
}
let result = applyVariant(node, variantAst, designSystem.variants)
if (result === null) {
throw new Error(`Cannot use \`@variant\` with variant: ${variant}`)
}
変更後:
let nodes: AstNode[] = []
let compoundVariants = segment(variantNode.params, ',')
for (let [idx, compoundVariant] of compoundVariants.entries()) {
let node = styleRule(
'&',
idx === compoundVariants.length - 1
? variantNode.nodes
: variantNode.nodes.map(cloneAstNode),
)
let stackedVariants = segment(compoundVariant, ':')
for (let i = stackedVariants.length - 1; i >= 0; --i) {
let variant = stackedVariants[i].trim()
let variantAst = designSystem.parseVariant(variant)
if (variantAst === null) {
throw new Error(`Cannot use \`@variant\` with unknown variant: ${variant}`)
}
let result = applyVariant(node, variantAst, designSystem.variants)
if (result === null) {
throw new Error(`Cannot use \`@variant\` with variant: ${variant}`)
}
}
nodes.push(node)
}
コンマ区切りのコンパウンドバリアントはそれぞれ独立したルールセットに展開されます。最後のコンパウンドバリアント以外では cloneAstNode を使ってノードを複製することで、各ルールが独立したAST表現を持つよう設計されています。これはソースマップのdst(出力先)位置情報を正確に記録するためにも必要な処理です。
コロン区切りのスタックバリアントは、右から左へ(内側から外側へ)ループしながら applyVariant を順に適用することで、ネスト構造を内部的に再現します。この処理順はTailwind CSSのクラス記法a:b:flexの解釈と一致します。
設計判断
「概念的な展開(conceptual expansion)」としてのセマンティクス が本機能の中心的な設計思想です。新構文は既存の冗長な記述に内部展開される糖衣構文として定義されており、動作の予測可能性を維持しています。
PRでは、コンパウンドバリアントとスタックバリアントを組み合わせた場合にCSS出力が大きく膨張する可能性が明示的に言及されています。@variant a, b:c { @variant d, e:f { … } } のような組み合わせは、展開後に多数のルールセットが生成されます。この設計は利便性と引き換えに出力サイズのトレードオフを伴うため、利用者が意識的に管理する必要があります。
後方互換性は完全に維持されており、既存の手動ネスト構文は引き続き動作します。新旧の記法を混在させることも可能です。
まとめ
@variantのコンマ区切りとコロン区切りのサポートにより、これまで冗長なネストや重複記述が必要だった複数バリアントの適用が、Tailwind CSSのクラス記法と統一されたシンタックスで簡潔に書けるようになりました。既存コードとの完全な後方互換性を保ちながら、CSSの可読性と保守性を高める実用的な改善です。