`@utility`名でエスケープ文字を許容し、Biomeなどのフォーマッターとの互換性を向上

tailwindlabs/tailwindcss

#19607で報告された、@utilityでエスケープ済みのユーティリティ名が使えない問題が解消されました。これにより、BiomeなどのCSSパーサーとの互換性を保ちながら、スラッシュやパーセント記号を含むユーティリティを定義できるようになります。

背景

Tailwind CSS v4では、@utilityディレクティブでカスタムユーティリティを定義できます。@utility push-1/2のようにスラッシュを含む名前を指定すると、Tailwind CSSが自動的にエスケープして.push-1\/2というクラスを生成していました。

しかし、CSSの仕様上スラッシュは演算子として扱われるため、@utility push-1/2という記述は構文的に不正です。Biomeのような厳格なCSSパーサーを使用すると、この記述でパースエラーが発生していました。回避策として@utility push-1\/2のように事前にエスケープすると、今度はTailwind CSSが「Utilities should be alphanumeric and start with a lowercase letter」というエラーを返していました。

この問題により、コードフォーマッターやリンターのCSSパース機能と、Tailwind CSSの独自構文の両立が困難になっていました。

技術的な変更

packages/tailwindcss/src/utilities.tscreateCssUtility関数が修正され、@utilityのパラメータからエスケープ文字を除去するようになりました。

変更前:

export function createCssUtility(node: AtRule) {
  let name = node.params
  // ...
}

変更後:

export function createCssUtility(node: AtRule) {
  // Allow escaped characters in the name for compatibility with formatters and
  // other parsers, to ensure valid CSS syntax. E.g.: `@utility foo-1\/2`.
  //
  // Note: the actual utility will be `foo-1/2`
  let name = unescape(node.params)
  // ...
}

unescape関数により、@utility push-1\/2というパラメータからpush-1/2という名前が抽出されます。この名前に対して既存のエスケープ処理が適用されるため、最終的に.push-1\/2というクラスが生成されます。

追加されたテストケースでは、スラッシュとパーセント記号の両方のエスケープに対応していることが検証されています:

test('@utility can handle escape sequences correctly', async () => {
  let { build } = await compile(css`
    @layer utilities {
      @tailwind utilities;
    }

    @utility push-1\/2 {
      right: 50%;
    }

    @utility push-50\% {
      right: 50%;
    }
  `)
  let compiled = build(['push-1/2', 'push-50%'])

  expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
    "@layer utilities {
      .push-1\\/2, .push-50\\% {
        right: 50%;
      }
    }"
  `)
})

@utility push-1\/2@utility push-50\%の両方が正しく処理され、同一の出力を生成することが確認できます。

設計判断

入力段階でのエスケープ除去という実装方式が採用されました。

PR内のコメントでは「バックスラッシュを単純に無視すればよい」と述べられています。これは、エスケープ文字を取り除いた後は既存の処理フローをそのまま利用できるという判断です。unescape関数を追加するだけで、名前の検証ロジックやクラス生成ロジックに変更を加える必要がありません。

この設計により、@utility push-1/2@utility push-1\/2は完全に同じ結果を生成します。開発者は、使用するツールチェーンに応じてどちらの記法も選択できます。Biomeのような厳格なパーサーを使う場合はエスケープ済みの記法を、そうでない場合は読みやすい非エスケープ記法を使えます。

本PRは、Tailwind CSSの構文を拡張するのではなく、既存の構文の入力形式を柔軟にすることで、エコシステム全体との互換性を高めた変更といえます。@utilityの名前処理にワンステップ追加するだけで、多様なツールチェーンとの共存が可能になりました。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)、背景・技術的な変更(各論)、まとめ(結論)の3部構成が明確に適用されており、理想的な記事構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト、GitHubのIssue/PRへのリンク記法ともに正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Tailwind CSSの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが「総論→各論」で構成され、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロック(`utilities.ts`の変更、`index.test.ts`の追加)は、提供されたDiffの内容と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`@utility`ディレクティブ、CSSパーサー、エスケープ文字など、使用されている技術用語は文脈に即しており正確です。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

エスケープ文字を許容する変更が、なぜフォーマッターとの互換性向上に繋がるのか、その技術的な理由が論理的かつ正確に説明されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべてPRのDescription(「simply ignoring backslashes」など)や、PRが参照するIssue番号(#19607)によって裏付けられています。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#19626)やIssue番号(#19607)などの数値・固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRの「handle backslash in `@utility` name」という技術的な変更を、「フォーマッターとの互換性向上」という読者にとっての価値に変換して表現しており、PRの内容と一致しつつ、より魅力的です。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事内で言及されている「Biome」はPRのDescriptionには直接記載がありませんが、PRが解決するIssue #19607で明確に言及されているため、文脈を補うための妥当な情報であり、捏造ではありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「問題が解消されました」といった過去形の表現が使われており、マージ済みのPRに関する報告として時間表現は正確です。