T-CREATOR

Tailwind CSS バリアント辞典:aria-[]/data-[]/has-[]/supports-[] を一気に使い倒す

Tailwind CSS バリアント辞典:aria-[]/data-[]/has-[]/supports-[] を一気に使い倒す

Tailwind CSS には、条件付きスタイルを適用するための強力なバリアント機能が用意されています。その中でも aria-[]data-[]has-[]supports-[] は、動的な UI 制御やブラウザ互換性対応において非常に重要な役割を果たします。

これらのバリアントを活用することで、JavaScript を一切書かずに、HTML 属性の変化に応じたスタイル切り替えや、子要素の状態による親要素のスタイル制御が実現できます。本記事では、これら 4 つのバリアントに絞って、使い方から実践的なテクニックまで徹底的に解説していきます。

バリアント早見表

以下は、本記事で扱う 4 つのバリアントの基本情報をまとめた早見表です。

#バリアント用途構文例対応するセレクタ
1aria-[]ARIA 属性による状態管理aria-[expanded="true"]:rotate-180[aria-expanded="true"]
2data-[]カスタムデータ属性による制御data-[state=active]:bg-blue-500[data-state="active"]
3has-[]子要素の状態による親要素制御has-[:checked]:border-green-500:has(:checked)
4supports-[]CSS 機能のブラウザ対応確認supports-[display:grid]:grid@supports (display: grid)

それぞれのバリアントには独自の強みがあり、組み合わせることでより柔軟なスタイリングが可能になります。では、一つずつ詳しく見ていきましょう。

背景

従来の CSS における条件付きスタイリングの課題

Web アプリケーションでは、ユーザーの操作やコンポーネントの状態に応じて、見た目を動的に変化させる必要があります。従来の CSS では、こうした動的なスタイリングを実現するために、いくつかの手法が使われてきました。

一つ目は、JavaScript で直接 style 属性を操作する方法です。これは確実に動作しますが、インラインスタイルが増えすぎると保守性が低下し、CSS の詳細度(Specificity)の問題も引き起こします。

二つ目は、状態に応じて CSS クラスを付け替える方法です。こちらはスタイルと状態を分離できるため、保守性は向上しますが、クラス名の管理が煩雑になり、コンポーネントごとに個別のクラスを定義する必要がありました。

属性セレクタとプログレッシブエンハンスメントの台頭

近年、HTML5 の data-* 属性や ARIA 属性が広く使われるようになり、属性セレクタを使った CSS が注目されています。属性セレクタを使うと、HTML の意味的な構造を保ちながら、状態に応じたスタイリングが可能になります。

また、CSS の :has() 疑似クラスや @supports ルールといった新しい機能が登場し、より宣言的で直感的なスタイリングが可能になりました。これらの機能は、プログレッシブエンハンスメント(段階的な機能強化)の考え方と相性が良く、モダンブラウザでは高度な機能を提供しつつ、古いブラウザでも基本機能を保つことができます。

以下の図は、従来の手法とモダンな属性ベースのスタイリングの違いを示しています。

mermaidflowchart TB
  old["従来の手法"] --> js["JavaScript で<br/>style 操作"];
  old --> classNode["クラス名で<br/>状態管理"];
  js --> issue1["詳細度の問題"];
  classNode --> issue2["クラス名の増加"];

  modern["モダンな手法"] --> attr["属性セレクタ<br/>による制御"];
  modern --> pseudoClass["疑似クラス<br/>(:has など)"];
  attr --> benefit1["HTML の<br/>意味的構造を保持"];
  pseudoClass --> benefit2["宣言的で<br/>直感的"];

上図のように、モダンな手法では HTML の構造と CSS の役割が明確に分離され、保守性の高いコードが書けるようになっています。

Tailwind CSS におけるバリアント機能の進化

Tailwind CSS は、ユーティリティファーストという思想のもと、あらかじめ定義された小さなクラスを組み合わせてスタイリングを行うフレームワークです。バージョン 3 以降、任意の値(Arbitrary Values)や任意のバリアント(Arbitrary Variants)が導入され、柔軟性が大幅に向上しました。

特に、aria-[]data-[]has-[]supports-[] といった高度なバリアントは、従来 CSS で実現していた条件付きスタイリングを、Tailwind CSS の構文内で完結できるようになったことを意味します。これにより、コンポーネントの状態管理がより直感的になり、コードの可読性も向上しました。

課題

JavaScript に依存したスタイル制御の限界

多くの Web アプリケーションでは、状態管理を JavaScript で行い、それに応じてクラス名を動的に切り替える手法が一般的です。しかし、この方法にはいくつかの課題があります。

まず、JavaScript のバンドルサイズが増大し、初期表示速度が遅くなる可能性があります。特に React や Vue などのフレームワークを使う場合、状態管理ライブラリやロジックが複雑になりがちです。

次に、アクセシビリティの観点から、ARIA 属性と CSS の状態が二重管理になることが問題です。たとえば、ドロップダウンメニューの開閉状態を aria-expanded 属性と CSS クラスの両方で管理すると、同期ミスが発生しやすくなります。

さらに、ブラウザの CSS 機能対応状況を JavaScript で判定する必要があり、フィーチャー検出のコードが増えてしまいます。

クラス名の爆発的増加と命名規則の複雑化

Tailwind CSS を使わない場合、状態ごとに個別の CSS クラスを定義する必要があります。たとえば、アコーディオンコンポーネントを作る場合、以下のようなクラス名が必要になるでしょう。

typescript// クラス名の例(Tailwind CSS を使わない場合)
const classes = [
  'accordion',
  'accordion--open',
  'accordion--closed',
  'accordion__header',
  'accordion__header--active',
  'accordion__content',
  'accordion__content--visible',
];

上記のように、BEM などの命名規則を使っても、クラス名が増え続け、管理が煩雑になります。また、CSS ファイルのサイズも大きくなり、メンテナンスコストが上がります。

子要素の状態による親要素のスタイリングの困難さ

従来の CSS では、子要素の状態に応じて親要素のスタイルを変更することが非常に困難でした。CSS セレクタは基本的に「親から子」「兄から弟」という方向にしか適用できないため、「子の状態を見て親のスタイルを変える」という処理は JavaScript に頼らざるを得ませんでした。

たとえば、フォーム内にチェックボックスがあり、チェックされたら親のカード要素の背景色を変えたい場合、以下のような JavaScript が必要でした。

javascript// 従来の方法:JavaScript による親要素の制御
const checkbox = document.querySelector(
  'input[type="checkbox"]'
);
const card = checkbox.closest('.card');

checkbox.addEventListener('change', (e) => {
  if (e.target.checked) {
    card.classList.add('card--checked');
  } else {
    card.classList.remove('card--checked');
  }
});

このコードは動作しますが、DOM 操作が発生するためパフォーマンスへの影響が懸念され、複数の要素に対して同様の処理を行うと、コードが冗長になります。

以下の図は、子要素の状態による親要素のスタイリングにおける従来の課題を示しています。

mermaidflowchart TB
  child["子要素の状態変化"] --> js["JavaScript で<br/>検知・処理"]
  js --> dom["DOM 操作で<br/>親にクラス追加"]
  dom --> issue1["パフォーマンス<br/>への影響"]
  dom --> issue2["コードの<br/>冗長化"]

  modern["モダンな手法<br/>(:has 疑似クラス)"] --> css["CSS のみで<br/>親要素を制御"]
  css --> benefit["JavaScript<br/>不要"]

上図のように、CSS の :has() 疑似クラスを使えば、JavaScript を書かずに親要素のスタイルを制御できるようになります。

解決策

aria-[] バリアント:ARIA 属性による宣言的な状態管理

aria-[] バリアントは、ARIA(Accessible Rich Internet Applications)属性の値に応じてスタイルを適用します。ARIA 属性はアクセシビリティのために用いられますが、同時に要素の状態を表現する意味的な属性でもあります。

この方式の最大のメリットは、アクセシビリティとスタイリングが一元化されることです。たとえば、aria-expanded 属性でドロップダウンの開閉状態を管理すれば、スクリーンリーダーにも状態が伝わり、同時に CSS でアイコンの回転などを制御できます。

構文は以下の通りです。

html<!-- aria-[] バリアントの基本構文 -->
<button
  aria-expanded="false"
  class="aria-[expanded=true]:rotate-180"
>
  <svg><!-- アイコン --></svg>
</button>

上記の例では、aria-expanded="true" になったときにアイコンが 180 度回転します。JavaScript は状態の切り替えのみを担当し、スタイルは Tailwind CSS のクラスで完結します。

data-[] バリアント:カスタム属性による柔軟な制御

data-[] バリアントは、HTML5 の data-* 属性を使った条件付きスタイリングを可能にします。data-* 属性は、開発者が自由に定義できるカスタム属性であり、コンポーネントの状態やメタデータを保持するのに適しています。

aria-[] が主にアクセシビリティ関連の状態を扱うのに対し、data-[] はより汎用的な用途に使えます。たとえば、タブコンポーネントのアクティブ状態、スライダーの現在位置、モーダルの表示状態など、任意の状態を表現できます。

構文は以下の通りです。

html<!-- data-[] バリアントの基本構文 -->
<div
  data-state="active"
  class="data-[state=active]:bg-blue-500 data-[state=inactive]:bg-gray-300"
>
  タブコンテンツ
</div>

上記の例では、data-state 属性の値に応じて背景色が変化します。属性名や値は完全にカスタマイズ可能なため、プロジェクトの命名規則に合わせた柔軟な設計ができます。

has-[] バリアント:子要素の状態による親要素のスタイリング

has-[] バリアントは、CSS の :has() 疑似クラスを Tailwind CSS で使えるようにしたものです。これにより、子要素や子孫要素の状態に応じて、親要素のスタイルを変更できます。

従来は JavaScript でしか実現できなかった「子の状態を見て親を変える」処理が、純粋な CSS のみで可能になりました。これは、フォームのチェックボックスやラジオボタン、リスト項目の選択状態など、さまざまな場面で役立ちます。

構文は以下の通りです。

html<!-- has-[] バリアントの基本構文 -->
<label
  class="has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50"
>
  <input type="checkbox" class="mr-2" />
  チェックすると親要素のスタイルが変わります
</label>

上記の例では、チェックボックスがチェックされると、親の <label> 要素の枠線と背景色が変化します。JavaScript を一切使わずに、インタラクティブな UI が実現できます。

以下の図は、has-[] バリアントによる親子関係のスタイリングを示しています。

mermaidflowchart LR
  parent["親要素<br/>(label)"] --|内包|--> child["子要素<br/>(input:checked)"];
  child --|状態変化|--> trigger["チェック状態"];
  trigger --|CSS :has() で検知|--> styleNode["親要素の<br/>スタイル変更"];
  styleNode --|適用|--> parent;

上図のように、子要素の状態変化を CSS が検知し、親要素のスタイルを動的に変更します。

supports-[] バリアント:CSS 機能のブラウザ対応確認

supports-[] バリアントは、CSS の @supports ルールを Tailwind CSS で使えるようにしたものです。特定の CSS プロパティや値がブラウザでサポートされているかを確認し、サポートされている場合にのみスタイルを適用します。

プログレッシブエンハンスメントを実現するために非常に有用で、モダンブラウザでは Grid レイアウトを使い、古いブラウザでは Flexbox にフォールバックする、といった使い方ができます。

構文は以下の通りです。

html<!-- supports-[] バリアントの基本構文 -->
<div
  class="
  supports-[display:grid]:grid
  supports-[display:grid]:grid-cols-3
  flex
"
>
  Grid 対応ブラウザでは Grid、非対応では Flex
</div>

上記の例では、display: grid がサポートされていれば Grid レイアウトを使い、サポートされていなければ Flexbox が適用されます。

具体例

aria-[] バリアント:アコーディオンコンポーネント

アコーディオンは、クリックすることでコンテンツが展開・収納される UI パターンです。ARIA 属性を活用することで、アクセシビリティとスタイリングを同時に実現できます。

まず、基本的な HTML 構造を作成します。

html<!-- アコーディオンの HTML 構造 -->
<div class="max-w-md mx-auto space-y-2">
  <div class="border rounded-lg">
    <button
      aria-expanded="false"
      aria-controls="content-1"
      class="w-full px-4 py-3 flex justify-between items-center"
    >
      <span class="font-medium">セクション 1</span>
      <svg
        class="w-5 h-5 transition-transform duration-200 aria-[expanded=true]:rotate-180"
        fill="none"
        stroke="currentColor"
        viewBox="0 0 24 24"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M19 9l-7 7-7-7"
        />
      </svg>
    </button>
  </div>
</div>

上記のコードでは、aria-expanded 属性を使ってボタンの展開状態を管理しています。矢印アイコンには aria-[expanded=true]:rotate-180 クラスを適用し、展開時に 180 度回転させます。

次に、コンテンツ部分を実装します。

html    <!-- アコーディオンのコンテンツ部分 -->
    <div
      id="content-1"
      aria-hidden="true"
      class="
        overflow-hidden transition-all duration-200
        aria-[hidden=true]:max-h-0
        aria-[hidden=false]:max-h-96
      "
    >
      <div class="px-4 py-3 border-t">
        <p class="text-gray-600">
          ここにアコーディオンの詳細コンテンツが入ります。
          aria-hidden 属性によって表示・非表示が切り替わります。
        </p>
      </div>
    </div>
  </div>
</div>

上記では、aria-hidden 属性に応じて max-h-0max-h-96 を切り替えることで、スムーズな開閉アニメーションを実現しています。

最後に、JavaScript で状態切り替えを実装します。

javascript// アコーディオンの状態切り替え(JavaScript)
const buttons = document.querySelectorAll(
  '[aria-controls]'
);

buttons.forEach((button) => {
  button.addEventListener('click', () => {
    // 現在の展開状態を取得
    const isExpanded =
      button.getAttribute('aria-expanded') === 'true';

    // 状態を反転
    button.setAttribute('aria-expanded', !isExpanded);

    // 対応するコンテンツの aria-hidden も切り替え
    const contentId = button.getAttribute('aria-controls');
    const content = document.getElementById(contentId);
    content.setAttribute('aria-hidden', isExpanded);
  });
});

上記の JavaScript は状態の切り替えのみを担当し、スタイリングは完全に Tailwind CSS に任せています。これにより、スタイルとロジックが明確に分離され、保守性が向上します。

data-[] バリアント:タブ切り替えコンポーネント

タブ切り替えは、複数のコンテンツを切り替えて表示する UI パターンです。data-* 属性を使うことで、アクティブなタブを明確に管理できます。

まず、タブボタンの HTML を作成します。

html<!-- タブボタンの実装 -->
<div class="max-w-2xl mx-auto">
  <div class="flex border-b">
    <button
      data-tab="overview"
      data-state="active"
      class="
        px-6 py-3 font-medium transition-colors
        data-[state=active]:text-blue-600
        data-[state=active]:border-b-2
        data-[state=active]:border-blue-600
        data-[state=inactive]:text-gray-500
        data-[state=inactive]:hover:text-gray-700
      "
    >
      概要
    </button>
  </div>
</div>

上記のコードでは、data-state 属性で activeinactive を切り替え、それぞれに対応するスタイルを適用しています。アクティブなタブには青い下線が表示されます。

続いて、他のタブボタンも実装します。

html    <!-- 残りのタブボタン -->
    <button
      data-tab="features"
      data-state="inactive"
      class="
        px-6 py-3 font-medium transition-colors
        data-[state=active]:text-blue-600
        data-[state=active]:border-b-2
        data-[state=active]:border-blue-600
        data-[state=inactive]:text-gray-500
        data-[state=inactive]:hover:text-gray-700
      "
    >
      機能
    </button>
    <button
      data-tab="pricing"
      data-state="inactive"
      class="
        px-6 py-3 font-medium transition-colors
        data-[state=active]:text-blue-600
        data-[state=active]:border-b-2
        data-[state=active]:border-blue-600
        data-[state=inactive]:text-gray-500
        data-[state=inactive]:hover:text-gray-700
      "
    >
      料金
    </button>
  </div>

次に、タブコンテンツを実装します。

html  <!-- タブコンテンツの実装 -->
  <div class="mt-4">
    <div
      data-tab-content="overview"
      data-state="active"
      class="data-[state=active]:block data-[state=inactive]:hidden"
    >
      <h3 class="text-xl font-bold mb-2">概要</h3>
      <p class="text-gray-600">製品の概要をご紹介します。</p>
    </div>
    <div
      data-tab-content="features"
      data-state="inactive"
      class="data-[state=active]:block data-[state=inactive]:hidden"
    >
      <h3 class="text-xl font-bold mb-2">機能</h3>
      <p class="text-gray-600">主要な機能一覧です。</p>
    </div>
    <div
      data-tab-content="pricing"
      data-state="inactive"
      class="data-[state=active]:block data-[state=inactive]:hidden"
    >
      <h3 class="text-xl font-bold mb-2">料金</h3>
      <p class="text-gray-600">料金プランをご確認ください。</p>
    </div>
  </div>
</div>

上記では、data-state 属性に応じて blockhidden を切り替え、対応するコンテンツのみを表示しています。

最後に、タブ切り替えの JavaScript を実装します。

javascript// タブ切り替えの JavaScript 実装
const tabButtons = document.querySelectorAll('[data-tab]');

tabButtons.forEach((button) => {
  button.addEventListener('click', () => {
    // すべてのタブを inactive に設定
    tabButtons.forEach((btn) =>
      btn.setAttribute('data-state', 'inactive')
    );

    // クリックされたタブを active に設定
    button.setAttribute('data-state', 'active');

    // すべてのコンテンツを非表示
    const contents = document.querySelectorAll(
      '[data-tab-content]'
    );
    contents.forEach((content) =>
      content.setAttribute('data-state', 'inactive')
    );

    // 対応するコンテンツを表示
    const targetTab = button.getAttribute('data-tab');
    const targetContent = document.querySelector(
      `[data-tab-content="${targetTab}"]`
    );
    targetContent.setAttribute('data-state', 'active');
  });
});

上記の JavaScript では、クリックされたタブを active にし、他のタブを inactive にすることで、スタイルの切り替えを実現しています。

has-[] バリアント:選択可能なカードリスト

選択可能なカードリストは、ユーザーが複数の選択肢から選ぶ UI パターンです。has-[] バリアントを使うと、チェックボックスの状態に応じてカード全体のスタイルを変更できます。

まず、カードの HTML 構造を作成します。

html<!-- 選択可能なカードの実装 -->
<div
  class="max-w-3xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4"
>
  <label
    class="
    block p-6 border-2 rounded-lg cursor-pointer transition-all
    has-[:checked]:border-blue-500
    has-[:checked]:bg-blue-50
    has-[:checked]:shadow-lg
    hover:shadow-md
  "
  >
    <div class="flex items-start gap-3">
      <input
        type="checkbox"
        name="plan"
        value="basic"
        class="mt-1"
      /></div
  ></label>
</div>

上記のコードでは、has-[:checked] バリアントを使い、チェックボックスがチェックされたときにカード全体の枠線、背景色、影を変化させています。

続いて、カードの内容を実装します。

html      <!-- カードの内容 -->
      <div class="flex-1">
        <h3 class="text-lg font-bold mb-1">ベーシックプラン</h3>
        <p class="text-gray-600 text-sm mb-3">
          個人利用に最適な基本プランです。
        </p>
        <div class="flex items-baseline gap-1">
          <span class="text-2xl font-bold">¥980</span>
          <span class="text-gray-500 text-sm">/月</span>
        </div>
      </div>
    </div>
  </label>

次に、2 つ目のカードも実装します。

html  <!-- 2つ目のカード -->
  <label class="
    block p-6 border-2 rounded-lg cursor-pointer transition-all
    has-[:checked]:border-blue-500
    has-[:checked]:bg-blue-50
    has-[:checked]:shadow-lg
    hover:shadow-md
  ">
    <div class="flex items-start gap-3">
      <input
        type="checkbox"
        name="plan"
        value="pro"
        class="mt-1"
      >
      <div class="flex-1">
        <h3 class="text-lg font-bold mb-1">プロプラン</h3>
        <p class="text-gray-600 text-sm mb-3">
          ビジネス利用に最適な高機能プランです。
        </p>
        <div class="flex items-baseline gap-1">
          <span class="text-2xl font-bold">¥2,980</span>
          <span class="text-gray-500 text-sm">/月</span>
        </div>
      </div>
    </div>
  </label>
</div>

上記の実装では、JavaScript を一切使わずに、チェックボックスの状態に応じてカード全体のスタイルが変化します。ユーザーがチェックを入れると、視覚的なフィードバックが即座に提供されます。

さらに高度な例として、ラジオボタンと組み合わせたバリエーションも実装できます。

html<!-- ラジオボタンを使った排他的選択カード -->
<div class="max-w-3xl mx-auto space-y-3">
  <label
    class="
    flex items-center gap-4 p-4 border-2 rounded-lg cursor-pointer
    has-[:checked]:border-green-500
    has-[:checked]:bg-green-50
  "
  >
    <input
      type="radio"
      name="shipping"
      value="standard"
      class="w-4 h-4"
    />
    <div class="flex-1">
      <div class="font-medium">通常配送</div>
      <div class="text-sm text-gray-600">
        3-5 営業日でお届け(無料)
      </div>
    </div>
  </label>

  <label
    class="
    flex items-center gap-4 p-4 border-2 rounded-lg cursor-pointer
    has-[:checked]:border-green-500
    has-[:checked]:bg-green-50
  "
  >
    <input
      type="radio"
      name="shipping"
      value="express"
      class="w-4 h-4"
    />
    <div class="flex-1">
      <div class="font-medium">速達配送</div>
      <div class="text-sm text-gray-600">
        翌日お届け(¥500)
      </div>
    </div>
  </label>
</div>

上記のように、ラジオボタンでも同様に has-[:checked] バリアントが使えます。排他的な選択が必要な場面で役立ちます。

supports-[] バリアント:レスポンシブグリッドレイアウト

Grid レイアウトは、モダンブラウザでは強力なレイアウト機能を提供しますが、古いブラウザでは対応していません。supports-[] バリアントを使うことで、プログレッシブエンハンスメントを実現できます。

まず、基本的な Grid レイアウトを実装します。

html<!-- Grid レイアウトのプログレッシブエンハンスメント -->
<div
  class="
  max-w-6xl mx-auto p-4
  flex flex-wrap gap-4
  supports-[display:grid]:grid
  supports-[display:grid]:grid-cols-1
  supports-[display:grid]:md:grid-cols-3
  supports-[display:grid]:gap-6
"
>
  <div
    class="
    w-full sm:w-[calc(50%-0.5rem)]
    supports-[display:grid]:w-auto
    p-6 bg-white rounded-lg shadow
  "
  >
    <h3 class="font-bold mb-2">カード 1</h3>
    <p class="text-gray-600">
      Grid 対応ブラウザでは Grid、非対応では Flexbox
      で表示されます。
    </p>
  </div>
</div>

上記のコードでは、デフォルトで Flexbox を使い、Grid がサポートされている場合のみ Grid レイアウトに切り替えています。supports-[display:grid]:w-auto で、Grid 時に Flexbox 用の幅指定を解除しています。

続いて、他のカードも実装します。

html  <!-- 残りのカード -->
  <div class="
    w-full sm:w-[calc(50%-0.5rem)]
    supports-[display:grid]:w-auto
    p-6 bg-white rounded-lg shadow
  ">
    <h3 class="font-bold mb-2">カード 2</h3>
    <p class="text-gray-600">古いブラウザでも基本的なレイアウトは維持されます。</p>
  </div>

  <div class="
    w-full sm:w-[calc(50%-0.5rem)]
    supports-[display:grid]:w-auto
    p-6 bg-white rounded-lg shadow
  ">
    <h3 class="font-bold mb-2">カード 3</h3>
    <p class="text-gray-600">プログレッシブエンハンスメントの実践例です。</p>
  </div>
</div>

さらに、最新の CSS 機能を使った例も見てみましょう。

html<!-- Subgrid のサポート確認 -->
<div
  class="
  supports-[grid-template-rows:subgrid]:grid
  supports-[grid-template-rows:subgrid]:grid-cols-2
  supports-[grid-template-rows:subgrid]:gap-4
  flex flex-col gap-4
"
>
  <div
    class="
    supports-[grid-template-rows:subgrid]:grid
    supports-[grid-template-rows:subgrid]:grid-rows-subgrid
    supports-[grid-template-rows:subgrid]:row-span-2
    p-4 bg-gray-100 rounded
  "
  >
    <h4 class="font-bold">サブグリッド対応</h4>
    <p class="text-sm text-gray-600">
      Subgrid
      がサポートされている場合、親グリッドの行を継承します。
    </p>
  </div>
</div>

上記では、CSS Grid の Subgrid 機能がサポートされているかを確認し、サポートされている場合のみ適用しています。このように、最新機能を安全に導入できます。

また、Container Queries のサポート確認もできます。

typescript// Container Queries のサポート確認(TypeScript 型定義)
type SupportedFeature =
  | 'display:grid'
  | 'display:flex'
  | 'grid-template-rows:subgrid'
  | 'container-type:inline-size';

// 実際の HTML での使用例
const containerQueryExample = `
  <div class="
    supports-[container-type:inline-size]:container
    w-full
  ">
    <div class="
      supports-[@container(min-width:400px)]:flex
      supports-[@container(min-width:400px)]:gap-4
      block space-y-2
    ">
      <div>コンテナのサイズに応じてレイアウトが変わります</div>
      <div>Container Queries 非対応ブラウザでは縦並びになります</div>
    </div>
  </div>
`;

上記のように、TypeScript で型定義を行うことで、サポート確認する機能を明示的に管理できます。

複数バリアントの組み合わせ:高度な UI パターン

これまで紹介したバリアントは、組み合わせて使うことでさらに強力になります。複雑な条件下でのスタイリングも、宣言的に記述できます。

まず、ドロップダウンメニューを実装します。

html<!-- 複数バリアントを組み合わせたドロップダウン -->
<div class="relative inline-block" data-dropdown>
  <button
    aria-expanded="false"
    aria-haspopup="true"
    data-state="closed"
    class="
      px-4 py-2 bg-blue-600 text-white rounded-lg
      aria-[expanded=true]:bg-blue-700
      data-[state=open]:shadow-lg
      has-[+div:hover]:bg-blue-700
    "
  >
    メニュー
    <svg
      class="inline-block w-4 h-4 ml-2 transition-transform aria-[expanded=true]:rotate-180"
      fill="none"
      stroke="currentColor"
      viewBox="0 0 24 24"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        stroke-width="2"
        d="M19 9l-7 7-7-7"
      />
    </svg>
  </button>
</div>

上記では、aria-expandeddata-statehas-[] を組み合わせて、ボタンの状態を多角的に管理しています。

続いて、メニューコンテンツを実装します。

html  <!-- ドロップダウンメニューの内容 -->
  <div
    aria-hidden="true"
    data-state="closed"
    class="
      absolute top-full left-0 mt-2 w-56
      bg-white rounded-lg shadow-xl border
      transition-all duration-200
      aria-[hidden=true]:opacity-0
      aria-[hidden=true]:invisible
      aria-[hidden=true]:scale-95
      aria-[hidden=false]:opacity-100
      aria-[hidden=false]:visible
      aria-[hidden=false]:scale-100
      data-[state=closed]:pointer-events-none
      supports-[backdrop-filter]:backdrop-blur-sm
      supports-[backdrop-filter]:bg-white/90
    "
  >
    <a href="#" class="block px-4 py-3 hover:bg-gray-100">プロフィール</a>
    <a href="#" class="block px-4 py-3 hover:bg-gray-100">設定</a>
    <a href="#" class="block px-4 py-3 hover:bg-gray-100">ログアウト</a>
  </div>
</div>

上記のコードでは、aria-hidden で表示・非表示を制御し、data-state でポインターイベントを制御し、supports-[] でブラウザサポートに応じた背景ぼかし効果を適用しています。

最後に、状態管理の JavaScript を実装します。

javascript// 複数バリアントを使ったドロップダウンの JavaScript
const dropdown = document.querySelector('[data-dropdown]');
const button = dropdown.querySelector('button');
const menu = dropdown.querySelector('div[aria-hidden]');

button.addEventListener('click', () => {
  const isExpanded =
    button.getAttribute('aria-expanded') === 'true';

  // ボタンの状態を更新
  button.setAttribute('aria-expanded', !isExpanded);
  button.setAttribute(
    'data-state',
    isExpanded ? 'closed' : 'open'
  );

  // メニューの状態を更新
  menu.setAttribute('aria-hidden', isExpanded);
  menu.setAttribute(
    'data-state',
    isExpanded ? 'closed' : 'open'
  );
});

// メニュー外をクリックしたら閉じる
document.addEventListener('click', (e) => {
  if (!dropdown.contains(e.target)) {
    button.setAttribute('aria-expanded', 'false');
    button.setAttribute('data-state', 'closed');
    menu.setAttribute('aria-hidden', 'true');
    menu.setAttribute('data-state', 'closed');
  }
});

上記のように、複数の属性を同期させることで、より堅牢な状態管理が実現できます。

まとめ

本記事では、Tailwind CSS の高度なバリアント機能である aria-[]data-[]has-[]supports-[] について詳しく解説しました。

aria-[] バリアントは、アクセシビリティとスタイリングを一元化し、スクリーンリーダー対応と視覚的な状態表現を同時に実現します。data-[] バリアントは、カスタム属性を使った柔軟な状態管理を可能にし、プロジェクト独自の命名規則に対応できます。

has-[] バリアントは、従来 JavaScript でしか実現できなかった「子の状態に応じた親のスタイリング」を CSS のみで実現し、インタラクティブな UI をシンプルに構築できます。supports-[] バリアントは、ブラウザの機能対応状況に応じたプログレッシブエンハンスメントを実現し、最新機能を安全に導入できます。

これらのバリアントを適切に使い分け、組み合わせることで、JavaScript への依存を減らしながら、保守性が高く、アクセシブルで、パフォーマンスに優れた Web アプリケーションを構築できるでしょう。

ぜひ、実際のプロジェクトでこれらのバリアントを活用し、より宣言的で直感的なスタイリングを体験してみてください。

関連リンク