関数型utilityの末尾ダッシュを再許可
Tailwind CSS 4.2.1では、@utility の名前検証ルールが緩和され、border--* のような二重ダッシュを含む関数型utilityが再び使用可能になりました。これは4.2.0で導入された厳格な検証が、実用的な命名パターンを誤って制限していたことへの修正です。
背景
Tailwind CSS 4.2.0では #19524 により @utility の名前検証が強化されました。この変更で、サフィックス -* を除去した後の名前(root)が - で終わる場合はエラーとなる制限が導入されました。
@utility border--* {
border-color: --value(--color-border-*, [color]);
}
このコードは border--0、border--1、border--2 といったクラス名を生成しますが、4.2.0では「無効なutility名」としてコンパイルエラーになっていました。二重ダッシュの命名パターンは、CSSプロパティと値のスケールを視覚的に分離するために本番環境のデザインシステムで実際に使用されていました。
制限の理由は、デフォルト値を使用した場合に border- という空の値を持つクラスとマッチする可能性への懸念でしたが、この懸念は既存の実装で対処されていました。candidate.ts の findRoots 関数(887行目)は既に空の値を拒否しており、Oxideスキャナも二重ダッシュの候補を正しく抽出していました。
技術的な変更
utilities.ts の isValidFunctionalUtilityName 関数から末尾ダッシュのチェックが削除されました。
変更前:
let root = match[0]
let value = name.slice(root.length)
// Root should not end in `-` if there is no value
//
// `tab-size--*`
// --------- Root
// -- Suffix
//
// Because with default values, this could match `tab-size-` which is invalid.
if (value.length === 0 && root.endsWith('-')) {
return false
}
// No remaining value is valid
//
// `tab-size-*`
// -------- Root
// -- Suffix
if (value.length === 0) {
return true
}
変更後:
let root = match[0]
let value = name.slice(root.length)
// No remaining value is valid
//
// `tab-size-*`
// -------- Root
// -- Suffix
//
// Backwards compatibility: a root ending in `-` was valid and correctly
// scanned by Oxide. This means that custom utilities can result in candidates
// such as `foo--bar`.
//
// We might want to revisit this for Tailwind CSS v5, but for now we have to
// make it backwards compatible.
//
// PR: https://github.com/tailwindlabs/tailwindcss/pull/19696
//
if (value.length === 0) {
return true
}
value.length === 0 && root.endsWith('-') の分岐が削除され、末尾がダッシュで終わる場合も単に true を返すようになりました。コメントには後方互換性のための変更であること、そしてTailwind CSS v5での再検討の可能性が明記されています。
ユニットテストでは ['foo--*', false] が ['foo--*', true] に更新され、統合テストには @utility border--* が正しくコンパイルされることを確認するテストケースが追加されました。
test('functional utility with double-dash separator', async () => {
let input = css`
@theme reference {
--color-border-0: #e5e7eb;
--color-border-1: #d1d5db;
--color-border-2: #9ca3af;
}
@utility border--* {
border-color: --value(--color-border-*, [color]);
}
@tailwind utilities;
`
expect(await compileCss(input, ['border--0', 'border--1', 'border--2']))
.toMatchInlineSnapshot(`
".border--0 {
border-color: var(--color-border-0, #e5e7eb);
}
.border--1 {
border-color: var(--color-border-1, #d1d5db);
}
.border--2 {
border-color: var(--color-border-2, #9ca3af);
}"
`)
expect(await compileCss(input, ['border--3'])).toEqual('')
})
このテストは、テーマ変数 --color-border-* と組み合わせて二重ダッシュutilityが正しく動作することを検証しています。
設計判断
過剰な検証ルールを削除する方式が採用されました。
PR内のコメントでは、末尾ダッシュの制限が「過剰修正」であったことが説明されています。Oxideスキャナと候補パーサの両方が二重ダッシュのケースを正しく処理しており、既存のテスト ("items--center", vec!["items--center"]) がその動作を確認していました。検証ルールの追加時に、実際の動作と整合性のない制限が導入されていたことになります。
コード内のコメントには、この命名パターンをTailwind CSS v5で再検討する可能性が示唆されていますが、4.2.1では後方互換性を優先する判断が明示されています。実際に本番環境で使用されているパターンを壊さないという実用的な選択といえます。
本PRは、セマンティックバージョニングの観点から適切な修正です。4.2.0で意図せず破壊的変更となっていた検証ルールを、マイナーバージョン内で修正することで、既存の実装パターンとの互換性を回復しています。