Skip to main content

Base UI × Tailwind CSS 実践ガイド: Dialog で学ぶロジックとデザインの分離、Storybook、3 層テスト

モーダルを 1 つ自作すると、見た目とは関係のないコードが増えます。開閉の状態、Esc キーで閉じる処理、開いたときにフォーカスを中へ移す処理、閉じたときに元のボタンへ戻す処理、スクリーンリーダーへ状態を伝える属性。どれも省けませんが、色や余白を決めるコードと同じファイルに混ざっていきます。

ヘッドレス UI は、この「見た目とは関係のないコード」だけを部品として提供するライブラリです。このガイドでは、その 1 つである Base UI でお知らせ用のダイアログを作ります。見た目は Tailwind CSS で付け、Storybook に載せて、3 種類のテストで確かめるところまでを通します。

このガイドについて​

題材はダイアログ 1 つです。そのダイアログを 2 つの軸で扱います。

  • 分ける: 振る舞い (開閉の状態、キーボード操作、フォーカス、WAI-ARIA の属性) は Base UI に任せ、見た目は Tailwind CSS で書きます。両者をつなぐのは、Base UI が DOM に出す data-* 属性です
  • 確かめる: 分けた結果を Storybook で見えるようにし、jsdom の単体テスト、Storybook 上のテスト、実ブラウザのテストの 3 層で確かめます。どの検証をどの層に置くかは、08 章で表にまとめます

掲載するコードは、すべて 1 つのプロジェクトに置き、テストか実行で動作を確かめたものです。コードブロックの上に出ているファイル名は、そのプロジェクトの中のパスです。

バージョンの前提

本ガイドのコードは次の版で動かしています。Base UI は v1.0.0 でパッケージ名が @base-ui-components/react から @base-ui/react に変わりました1。それより前に書かれた記事のコードは import 元が違います。

分類パッケージ版
UI ライブラリreact / react-dom19.3
ビルドvite8.3
言語typescript6.0
ヘッドレス UI@base-ui/react1.8
スタイルtailwindcss / @tailwindcss/vite4.3
カタログstorybook10.6
テストvitest / vitest-browser-react5.0 / 2.3
テスト@testing-library/react / @testing-library/user-event16.3 / 14.6
テストjsdom30.1
ブラウザの操作playwright / @playwright/test1.63
検証 (09 章)zod4.6
データ取得 (09 章)@tanstack/react-query5.104

Node.js は 24 を使っています。

Storybook の導入コマンドが入れるパッケージの版は、実行する時点で変わります。導入し直して確かめたときは Vitest 4.1 と Playwright 1.64 が入り、その版でも本ガイドのテストがすべて通りました。版の扱いは 04 章で説明します。

対象読者​

  • React と Tailwind CSS でコンポーネントを書いたことがあり、ヘッドレス UI はまだ使ったことがない人
  • モーダルやドロップダウンを自作して、状態やアクセシビリティのコードが膨らんだ経験がある人
  • Storybook とコンポーネントのテストについて、何をどの道具でどこまで書くかを決めかねている人

前提知識​

必須度知識補足
必須React で関数コンポーネントと props を書けるTypeScript で書きます
必須Tailwind CSS のクラスで余白や色を指定したことがあるhover: のような variant を使ったことがあれば足ります
推奨テストを 1 本でも書いて動かしたことがあるVitest でなくても構いません
不要Storybook、Playwright、ヘッドレス UI の経験導入から扱います

既存記事との棲み分け​

このガイドは既存のガイドの続きに当たります。重なる内容は書かず、次の表の参照先へ送ります。

観点参照先本ガイドが持つもの
モーダルを素の React で自作するPortalとModal同じ振る舞いを Base UI に任せたときの書き方と、任せた部分の確かめ方
Tailwind CSS の導入と基本のクラスTailwind CSS ガイドdata-* 属性の variant と、開閉に合わせた遷移
cva と cn、Radix UI の asChildshadcn/ui Button で学ぶデザインシステム設計Base UI の render prop による合成
役割で要素を取るテストの考え方振る舞いをテストするヘッドレス UI を対象にした 3 層の使い分け
Vitest の導入とマッチャー開発ツールチェーンjsdom と実ブラウザを 1 つの設定で併用する方法

章立て​

全 9 章を 4 つの部に分けています。

第 1 部 ロジックとデザインを分ける​

章扱うこと
01 なぜ Base UI と Tailwind CSS を組み合わせるのか自作のモーダルが抱える仕事と、ヘッドレス UI がそのうちどこを引き受けるか
02 ロジック層とデザイン層2 つの層の責務と、両者をつなぐ data-* 属性
03 Dialog を実装するお知らせダイアログの実装。状態も ARIA も書かずに済む理由

第 2 部 カタログにする​

章扱うこと
04 Storybook を導入する導入、Tailwind CSS の適用、a11y アドオン、CSF 3 での story の書き方

第 3 部 3 層でテストする​

章扱うこと
05 役割でテストするクラス名でなく役割で要素を探せる理由。純粋関数への切り出し。3 層の全体像
06 単体テストVitest と React Testing Library で、開閉とフォーカスを jsdom の上で確かめます
07 Storybook でテストするplay 関数、a11y の自動検査、Chromatic による見た目の差分検知
08 実ブラウザでテストするVitest Browser Mode と Playwright。どの検証をどの層に置くかの表

第 4 部 まとめ​

章扱うこと
09 まとめと次の一歩4 つの分担の振り返りと、フォームやデータ取得へ広げるときの分け方

このガイドで扱わないこと​

観点参照先
Dialog 以外の部品 (Menu、Select、Tooltip など)Base UI の公式ドキュメント
デザイントークンと、variant を持つ部品の設計shadcn/ui Button で学ぶデザインシステム設計
テストを先に書く進め方TDD 実践ガイド
Next.js などでのサーバーサイドレンダリング本ガイドの範囲外です
CI への組み込みと、Chromatic の運用や料金本ガイドの範囲外です

読み方​

順に読むと、1 つのダイアログが実装され、カタログに載り、テストで守られるところまで進みます。

テストだけに関心がある場合は 05 章から読めます。その場合も、テストの対象になる 03 章のコードには目を通してください。Storybook の導入だけを知りたい場合は 04 章が独立して読めます。

Footnotes​

  1. v1.0.0 のリリースノートに、パッケージ名を @base-ui/react へ変えたことが破壊的変更として載っています (base-ui.com)。 ↩