hexカラーの大文字小文字を区別しない正規化を実装
Tailwind CSSは、任意プロパティや任意値に指定された hexカラー を大文字小文字を区別せずにテーマカラーへマッピングできるようになりました。これにより bg-[#fff] と bg-[#FFF] が同一の bg-white に正規化され、開発者はカラー表記の統一性を意識せずに利用できます。
背景
任意プロパティや任意値で使用される hex カラーは CSS 仕様上 ケースインセンシティブ ですが、Tailwind の以前の実装では文字列の比較がそのまま行われていました。結果として、bg-[#3f3cbb] はテーマカラー color-brand-purple に正規化できても、bg-[#3F3CBB] は正規化されずにそのまま出力されました。この挙動は開発者にとって予期せぬサジェスト欠如を引き起こし、特にデザインツールが大文字表記を出力するケースで不便でした。
この問題は Issue #20295 にて報告され、同一色でもサジェストが消える不整合として認識されました。実装上は canonicalizeCandidates が生成したシグネチャで比較を行うため、ケースが揃っていないと別色と判断されていました。そこでケースインセンシティブな比較を導入し、すべての hex カラー表記を統一的に扱う修正が提案されました。
修正により、任意プロパティや任意値の hex カラーがテーマカラーと正しくマッチし、IDE のサジェストやビルド時の最適化が一貫して機能するようになります。
技術的な変更
今回の変更は hex カラー正規化ロジック を追加し、文字列比較前にすべて小文字化する点です。packages/tailwindcss/src/canonicalize-candidates.ts に新たに HEX_REGEX を導入し、AST の宣言ノードを走査して hex 値を toLowerCase() で置換しています。
@@ -2425,6 +2425,12 @@ function createUtilitySignatureCache(
})
}
+// #RGB
+// #RGBA
+// #RRGGBB
+// #RRGGBBAA
+const HEX_REGEX = /#(?:[a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{4}|[a-f0-9]{3})/gi
+
// Optimize the CSS AST to make it suitable for signature comparison. We want to
// expand declarations, ignore comments, sort declarations etc...
function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: SignatureOptions) {
@@
- else if (node.kind === 'declaration' && node.value) {
- // Leave CSS variables alone
- if (node.property[0] === '-' && node.property[1] === '-') return
-
- // Ensure hex colors are always lowercased
- {
- HEX_REGEX.lastIndex = 0
- node.value = node.value.replace(HEX_REGEX, (color) => color.toLowerCase())
- }
- }
+ else if (node.kind === 'declaration' && node.value) {
+ // Leave CSS variables alone
+ if (node.property[0] === '-' && node.property[1] === '-') return
+
+ // Ensure hex colors are always lowercased
+ {
+ HEX_REGEX.lastIndex = 0
+ node.value = node.value.replace(HEX_REGEX, (color) => color.toLowerCase())
+ }
+ }
*** End Patch
テストコードにも同様のケースを追加し、[color:#fff] と [color:#FFF]、bg-[#fff] と bg-[#FFF] がそれぞれ text-white と bg-white に正規化されることを検証しています。
@@
- // Arbitrary property to named utility (case insensitive)
- ['[color:#fff]', 'text-white'],
- ['[color:#FFF]', 'text-white'],
+ // Arbitrary property to named utility (case insensitive)
+ ['[color:#fff]', 'text-white'],
+ ['[color:#FFF]', 'text-white'],
@@
- // Arbitrary value with different casing
- ['bg-[#fff]', 'bg-white'],
- ['bg-[#FFF]', 'bg-white'],
+ // Arbitrary value with different casing
+ ['bg-[#fff]', 'bg-white'],
+ ['bg-[#FFF]', 'bg-white'],
これらのテストは新たに追加された正規化ロジックが既存の機能と衝突せず、期待通りに動作することを保証します。コードベースへの影響は canonicalizeAst の宣言処理に限定され、他の変換ロジックやキャッシュ構造は変更していません。
設計判断
ケースインセンシティブな比較を AST 変換段階で実装する という方針が採用されました。これは文字列比較が行われる前に統一的に処理でき、既存のシグネチャ生成ロジックを改変せずに済むためリスクが低くなります。代替案としては比較関数側で toLowerCase() を呼び出す方法も考えられましたが、AST 変換で正規化すれば他のプラグインや将来の拡張でも同様の恩恵が得られます。
このアプローチは 後方互換性 を保ちつつ、既存の bg-[#3f3cbb] などの動作に影響を与えません。HEX_REGEX の gi フラグにより全体検索が大文字小文字どちらでもマッチし、置換で小文字化するだけなのでパフォーマンスへの影響は最小です。
結果として、Tailwind CSS の任意カラー正規化が CSS 仕様に忠実になり、開発者体験が向上しました。今後、他のケースインセンシティブなリテラル(例: url() のエンコード)への拡張も同様のパターンで実装できる基盤が整いました。
まとめ
本 PR は hexカラーのケースインセンシティブな正規化 を canonicalizeAst に組み込み、任意プロパティ・任意値でのテーマカラー参照を統一的に扱えるようにしました。テスト追加により機能の回帰が防止され、既存ロジックへの影響は限定的です。結果として、Tailwind CSS のカラーサジェストが大文字小文字に依存しない堅牢なものとなります。