frontend-ui について
React 19、Tailwind CSS v4、shadcn/ui を使った SaaS UI の構築と監査を支援するフロントエンドスキルです。Next.js と Vite に対応し、コンポーネントやワークフローに加えて、アクセシビリティ、レスポンシブ設計、パフォーマンスも扱います。
主な機能
- 01Next.js と Vite の React 設定
- 02Tailwind CSS v4 と shadcn/ui のパターン
- 03SaaS ダッシュボードと料金ページのレイアウト
- 04WCAG 2.1 AA のアクセシビリティ指針
- 05フォーム、データ取得、パフォーマンスの確認
活用シーン
- 01SaaS ダッシュボードや料金ページの構築
- 02キーボードとスクリーンリーダーの対応監査
- 03検証付きフォームやデータテーブルの作成
- 04フロントエンドの Core Web Vitals の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scientiacapital/ skills --skill frontend-uiインストール後、Claude Code で/frontend-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonreference/accessibility-checklist.mdcomponent-patterns.mdform-patterns.mdperformance-optimization.mdsaas-dashboard.mdsaas-pricing-checkout.mdshadcn-setup.mdspa-routing.mdtailwind-v4-setup.mdvite-react-setup.mdtemplates/component-with-variants.tsxdashboard-layout.tsxform-with-server-action.tsxnextjs-tailwind-v4-setup.csspricing-page.tsxspa-app-layout.tsxvite-react-config.ts詳細
本番環境向け SaaS:ダッシュボード、料金ページ、データテーブル、オンボーディング、ロールベースの UI を対象とし、WCAG 2.1 AA のアクセシビリティと Core Web Vitals のパフォーマンスを組み込んでいます。
<quick_start>
セットアップ:Tailwind v4 + shadcn/ui
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app && npx shadcn@latest init
npx shadcn@latest add button card dialog table form
Tailwind v4 — CSS ファースト(tailwind.config.js なし)
/* app/globals.css */
@import "tailwindcss";
@theme inline {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0.042 264.695);
--color-primary-foreground: oklch(0.985 0 0);
--radius-lg: 0.5rem;
--radius-md: calc(var(--radius-lg) - 2px);
--radius-sm: calc(var(--radius-lg) - 4px);
}
コンポーネントの構造(shadcn/ui 2026)
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: { default: "bg-primary text-primary-foreground", outline: "border border-input" },
size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8" },
},
defaultVariants: { variant: "default", size: "default" },
}
)
// React 19: ref is a regular prop — no forwardRef
// data-slot: styling hook for parent overrides
function Button({ className, variant, size, ref, ...props }:
React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>) {
return <button ref={ref} data-slot="button"
className={cn(buttonVariants({ variant, size, className }))} {...props} />
}
cn() ユーティリティ
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
Vite SPA の代替案
npm create vite@latest my-app -- --template react-ts
cd my-app && npm i -D @tailwindcss/vite && npx shadcn@latest init
Next.js との主な違い:
@tailwindcss/viteプラグイン(postcss ではありません)— HMR が高速で、Vite とネイティブに統合VITE_環境変数プレフィックス(NEXT_PUBLIC_ではありません)。import.meta.envからアクセスします- クライアントのみ — Server Components はありません。データ取得には React Query を使用します
- コード分割には
dynamic()の代わりにReact.lazy()+<Suspense>を使用します - React Router v7 または TanStack Router によるルーティング(ファイルベースではありません)
Tailwind v4、shadcn/ui、コンポーネントパターン、アクセシビリティ、フォーム、パフォーマンスに関するガイダンスは、すべて Vite SPA にも同様に適用されます。実際に異なるのは、ルーティングとデータ取得のみです。
reference/vite-react-setup.md と reference/spa-routing.md を参照してください。
</quick_start>
<success_criteria> エンタープライズ SaaS フロントエンドが本番環境に対応している条件:
- アクセシブル: WCAG 2.1 AA — キーボード操作、スクリーンリーダー、フォーカス管理、コントラスト比 4.5:1
- 高パフォーマンス: 4G モバイルで LCP < 2.5s、INP < 200ms、CLS < 0.1
- レスポンシブ: モバイルファースト、320px~2560px に対応し、コンポーネントにはコンテナークエリを使用
- セキュア: XSS の脆弱性なし、CSP ヘッダー、ユーザーコンテンツをサニタイズ
- テーマ対応: CSS によるダークモード、@theme 内のデザイントークン、一貫した余白と色
- コンポーザブル: Server Components をデフォルトとし、クライアント境界をリーフコンポーネントに寄せる
- 型安全: TypeScript の strict モード、すべてのフォームで Zod によるバリデーション、
anyを使用しない </success_criteria>
<core_principles>
- サーバーファースト — Server Components をデフォルトにします。インタラクティブ性が必要な場合にのみ
"use client"を追加します。クライアント境界はリーフコンポーネントに寄せます。 - デフォルトでアクセシブル — まずセマンティック HTML(
<nav>、<main>、<article>)を使用します。ネイティブのセマンティクスでは不十分な場合にのみ ARIA を使用します。 - 設定よりコンポジション — 小さく、合成可能なコンポーネントを使用します。複雑な UI には Compound パターンを使用します。境界で Context を使用します。
- 段階的な情報開示 — まず重要な情報を示します。必要に応じて複雑さを明らかにし、認知負荷を軽減します。
- モバイルファースト — 最小画面向けに設計し、より大きな画面に向けて拡張します。コンポーネントにはコンテナークエリを使用します。タッチターゲットは 44px 以上にします。
- デザイントークン — すべての視覚的な値を CSS
@themeに定義します。ハードコードは避けます。知覚的な均一性には OKLCH を使用します。 - エンドツーエンドの型安全性 — Zod スキーマをクライアントとサーバーで共有します。手動で定義したインターフェースより
React.ComponentProps<>を使用します。 </core_principles>
<tailwind_v4>
Tailwind CSS v4 — v3 からの主な変更点
tailwind.config.jsなし — すべての設定は CSS の@themeディレクティブで行います@import "tailwindcss"—@tailwind base/components/utilitiesに代わるものです- OKLCH カラー — hex/HSL に代わる、知覚的に均一なカラーです
- コンテナークエリを標準搭載 —
@container、@md:、@lg:プレフィックス @source— CSS ネイティブのファイルスキャン(content配列に代わるものです)- CSS サイズを 70% 縮小 — 未使用のスタイルを自動的に除去します
@theme inline— shadcn/ui ブリッジ。ユーティリティを生成せずにトークンを使用できます
@theme {
--color-brand-500: oklch(0.55 0.15 250);
--font-sans: "Inter", system-ui, sans-serif;
--breakpoint-xs: 475px;
--animate-slide-in: slide-in 0.2s ease-out;
}
// Container queries — component-level responsive
<div className="@container">
<div className="grid grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3 gap-4">
{items.map(item => <Card key={item.id} {...item} />)}
</div>
</div>
移行: npx @tailwindcss/upgrade — reference/tailwind-v4-setup.md を参照してください。
</tailwind_v4>
<shadcn_ui>
shadcn/ui 2026
@theme inline— トークンを Tailwind v4 に橋渡ししますdata-slot— 属性ベースのスタイル設定フック(className のオーバーライドに代わるものです)forwardRefなし — React 19 では ref をプロパティとして渡しますtw-animate-css— v4 との互換性のため、tailwindcss-animateに代わるものです- Radix または Base UI — プリミティブライブラリを選択します
// data-slot: parent can target child styles
function Card({ className, ref, ...props }: React.ComponentProps<"div">) {
return <div ref={ref} data-slot="card" className={cn("rounded-xl border bg-card", className)} {...props} />
}
// Style from parent:
<div className="[&_[data-slot=card]]:shadow-lg">
<Card>...</Card>
</div>
ダークモード: .dark クラスによる CSS カスタムプロパティの切り替え。reference/shadcn-setup.md を参照してください。
</shadcn_ui>
<component_architecture>
サーバーコンポーネントとクライアントコンポーネント
| サーバーコンポーネント(デフォルト) | クライアントコンポーネント("use client") |
|---|---|
| 非同期データフェッチ、DB アクセス | useState、useEffect、イベントハンドラー |
| JS バンドルゼロ、シークレットへのアクセス | ブラウザー API、サードパーティのクライアントライブラリ |
ルール: "use client" は可能な限り最小のリーフに押し下げます。
// Server page with client island
export default async function DashboardPage() {
const metrics = await getMetrics()
return (
<main>
<KPICards data={metrics} /> {/* Server-rendered */}
<RevenueChart data={metrics} /> {/* Client island */}
</main>
)
}
主要なパターン
- 複合コンポーネント —
<Table>/<TableRow>/<TableCell>の名前空間による合成 - cva バリアント —
class-variance-authorityによる型安全なスタイルバリアント - React.ComponentProps — 手動のインターフェースを置き換え、ref を通常のプロパティとして扱います
- data-slot — 親子間のオーバーライドのための外部スタイリングフック
- ポリモーフィック(asChild) — 異なる要素としてレンダリングするための
Slotパターン - SPA コード分割 —
React.lazy()+<Suspense>が Next.js のdynamic()を置き換えます
完全な例については reference/component-patterns.md を参照してください。
</component_architecture>
<saas_patterns>
エンタープライズ SaaS パターン
ダッシュボード: サイドバー + ヘッダー + メイン
<div className="flex h-screen">
<Sidebar className="w-64 hidden lg:flex" />
<div className="flex-1 flex flex-col">
<Header /> {/* Search, user menu, notifications */}
<main className="flex-1 overflow-auto p-6">
<KPIGrid metrics={metrics} />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
<RevenueChart data={revenue} />
<ActivityFeed items={activities} />
</div>
</main>
</div>
</div>
料金プラン(3 層のコンバージョン)
アンカー(低) | コンバージョンターゲット(ハイライト、「Most Popular」) | エンタープライズ(カスタム)
月額/年額の切り替え、機能比較表、社会的証明。templates/pricing-page.tsx を参照してください。
データテーブル — ソート/フィルター/ページネーションには shadcn Table + TanStack Table
状態の三種 — すべてのデータコンポーネントには次が必要です: ローディング(Skeleton) | エラー(再試行アクション) | 空(ガイダンス)
ロールベース UI — 条件付きレンダリングのための hasPermission(user, "scope") ガード
reference/saas-dashboard.md と reference/saas-pricing-checkout.md を参照してください。
</saas_patterns>
セマンティック HTML を最優先 — <header>、<nav>、<main>、<article>、<section>、<footer>
| パターン | 実装 |
|---|---|
| キーボードナビゲーション | Tab/Shift+Tab、メニュー/タブ内の矢印キー、Escape で閉じる |
| フォーカス管理 | ダイアログ内でのトラップ、閉じる際の復元、スキップリンク |
| ARIA ライブリージョン | 動的コンテンツには aria-live="polite" |
| フォームエラー | aria-invalid、aria-describedby、role="alert" |
| ローディング状態 | ローディング中のボタンに aria-busy={true} |
| コントラスト | テキスト 4.5:1、UI コンポーネント 3:1(OKLCH の明度チャンネル) |
// Skip link
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-50">
Skip to main content
</a>
コンポーネントごとの ARIA パターンについては reference/accessibility-checklist.md を参照してください。
<state_management>
状態の決定ツリー
| 状態の種類 | 解決策 | 例 |
|---|---|---|
| URL 状態 | nuqs / useSearchParams | フィルター、ページネーション、タブ |
| サーバーデータ | React Query / SWR | API データ、ユーザープロフィール |
| ローカル UI | useState | フォーム入力、トグル |
| 親子間の共有 | 状態のリフトアップ / Context | アコーディオングループ |
| 複雑な横断的関心事 | Zustand | カート、ウィザード、通知 |
URL 状態を優先 — 共有可能で、ブックマーク可能で、リフレッシュ後も保持されます。 </state_management>
<data_fetching>
データフェッチ
| パターン | 使用場面 | 方法 |
|---|---|---|
| サーバーコンポーネント | デフォルト | async function Page() { const data = await db.query() } |
| Suspense ストリーミング | 遅いデータ | <Suspense fallback={<Skeleton/>}><SlowComponent/></Suspense> |
| サーバーアクション | ミューテーション | "use server" + revalidatePath() |
| React Query | クライアントのリアルタイム | useQuery({ queryKey, queryFn, refetchInterval }) |
| React Query(SPA) | クライアントのみのアプリ | ローダー付きの useQuery({ queryKey, queryFn }) — サーバーコンポーネントを置き換えます |
| </data_fetching> |
- 共有 Zod スキーマ — クライアント検証とサーバーアクションの唯一の信頼できる情報源
- React Hook Form —
zodResolverを使ったuseForm、mode: "onBlur" - shadcn Form —
<Form>/<FormField>/<FormItem>/<FormLabel>/<FormMessage> - サーバーアクション — サーバー上で
safeParse、フィールドエラーを返し、revalidatePath
const schema = z.object({
name: z.string().min(2),
email: z.string().email(),
})
reference/form-patterns.md と templates/form-with-server-action.tsx を参照してください。
| 指標 | 目標 | すぐにできる改善 |
|---|---|---|
| LCP < 2.5s | メインコンテンツが表示される | next/image with priority, next/font |
| INP < 200ms | 操作にすばやく応答する | dynamic() で大きなコンポーネントをコード分割する |
| CLS < 0.1 | レイアウトシフトがない | 画像/フォント用のスペースを確保する、スケルトンローダー |
Tailwind v4 は CSS を自動的に 70% 小さくします。reference/performance-optimization.md を参照してください。
アクセシビリティ
- すべてのインタラクティブ要素でキーボード操作が可能
- スクリーンリーダーがコンテンツの意味を適切に読み上げる
- フォーカスインジケーターが見える、スキップリンクがある
- 色のコントラスト >= 4.5:1(テキスト)、>= 3:1(UI)
パフォーマンス
- LCP < 2.5s、INP < 200ms、CLS < 0.1
- 画像には next/image、フォントには next/font を使用
- 大きなコンポーネントを dynamic() でコード分割
レスポンシブ対応
- 320px〜2560px で動作し、タッチターゲットは >= 44px
- 再利用可能なコンポーネントにはコンテナークエリを使用
セキュリティ
- サニタイズせずに生の HTML を注入しない
- CSP ヘッダー、クライアントとサーバーの両方で Zod による検証
UX
- すべてのデータ表示にローディング/エラー/空の状態を用意
- データ変更にはトースト、破壊的な操作には確認を用意
結果サイドカーの出力
最後のステップとして、~/.claude/skill-analytics/last-outcome-frontend-ui.json に書き込みます。
{"ts":"[UTC ISO8601]","skill":"frontend-ui","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"components_built":[n],"pages_created":[n],"a11y_checks_passed":[bool]},
"error":null,"session_id":"[YYYY-MM-DD]"}
一部のステージが失敗しても結果が生成された場合は、ステータスに "partial" を使用してください。出力が生成されなかった場合に限り、"error" を使用してください。