コンテンツにスキップ

0014. フロントエンド技術選定 (Vite + React + Tailwind + shadcn、ただし Select は native)

コンテキストと課題

Web フロント (apps/web) は、業務データの表示・編集 (一覧/フォーム/ダッシュボード) を多く持つ 管理画面。サーバ状態の同期、フォームのバリデーション、再利用可能な UI、ダーク/ライト両対応が要る。 モダンで枯れた組み合わせを選び、デザインシステムを一貫させたい。

決定

Vite + React 19 + TypeScript + Tailwind CSS v3 + shadcn/ui ベースのコンポーネント を採用する。 サーバ状態は React Query、フォームは React Hook Form + Zod。

  • ビルド/言語: Vite ^6 / React ^19.1 / TypeScript ^5.7
  • スタイル: Tailwind CSS v3 (^3.4.17)、darkMode: ['class']tailwindcss-animate。テーマは HSL の CSS 変数で定義 (フォントは Geist / Noto Sans JP)。
  • UI: shadcn/ui スタイル (コンポーネントをリポジトリに取り込む方式)。プリミティブとして Radix を 一部利用 (@radix-ui/react-dialog / react-alert-dialog / react-label / react-slot)。
  • サーバ状態 / 表: @tanstack/react-query ^5 / @tanstack/react-table ^8
  • フォーム / 検証: react-hook-form ^7 + zod ^3 (共有スキーマは @workspace/shared)。

例外: Select は shadcn ではなく native <select>

shadcn の Select は Radix UI 依存を増やすため採用せず、native <select> を Tailwind でスタイルした 簡易版 を使う (components/ui/select.tsx)。SVG のシェブロンを背景画像で付け、value/onChange は 通常の HTML select として扱う。

ダークモードと native コントロールの可視化 (color-scheme)

ダーク時に native フォームコントロール (<select> の選択値/option リスト、日付・数値 input のピッカー、 スクロールバー等) をテーマに追従させるため、index.csscolor-scheme を宣言する:

:root { color-scheme: light; /* … HSL 変数 … */ }
.dark { color-scheme: dark;  /* … HSL 変数 … */ }

これが無いと、.dark でブラウザが light スキームの文字色で描画し、dark に塗った背景に対し dark-on-dark となって「選択した文字列が見えない」症状になる。

影響 (Consequences)

良い点

  • 枯れたモダン構成で、React Query (サーバ状態) と RHF+Zod (フォーム) により状態/検証の定型が安定する。 Zod スキーマは API/Mobile と共有できる (ADR 0001)。
  • shadcn を「取り込み方式」で使うため、コンポーネントを完全に制御でき、デザインシステムを統一しやすい。
  • color-scheme の宣言で native コントロールがダークでも可視になり、Select の依存も増やさずに済む。

トレードオフ / 注意点

  • native <select> は shadcn の Radix Select に比べ、option の自由なレンダリング (アイコン/装飾) が できない。代わりに依存が減り、モバイルのネイティブ挙動が得られる。サイズ調整に癖があり、小さい高さ (例 h-8) では文字が見切れることがある (適切な高さを使う)。
  • shadcn の取り込み方式は 手動更新。上流の改善を取り込むには各コンポーネントを更新する必要がある。
  • React 19 / Tailwind v3 という比較的新しめのバージョンに追従するコストがある。
  • テスト環境では React のインスタンスが二重化しうるため、コンポーネント描画テストは vitest.config の alias 固定が必要 (ADR 0015)。

根拠 (典拠)

  • apps/web/package.json:16-41 — vite ^6.0.7 / react ^19.1.0 / typescript ^5.7.2 / tailwindcss ^3.4.17 / @tanstack/react-query ^5 / @tanstack/react-table ^8 / react-hook-form ^7 / zod ^3 / @radix-ui/*
  • apps/web/tailwind.config.ts:5-6,90darkMode: ['class'] / content / tailwindcss-animate
  • apps/web/src/components/ui/select.tsx:4-34 — native <select> を採用する理由のコメントと実装。
  • apps/web/src/index.css:17-24,58-60:root { color-scheme: light } / .dark { color-scheme: dark } と理由のコメント。
  • README.md:13-15 — Biome 採用 (ESLint/Prettier 不採用)、Web = Vite + React + TailwindCSS + shadcn/ui。