web-ui-shadcn-ui について
shadcn/uiでインターフェースを構築する開発者向けのガイドです。CLIによるコンポーネントの追加やソース編集に加え、テーマ変数、クラスの組み合わせ、フォームレイアウト、複合コンポーネントを扱います。
主な機能
- 01CLIでのコンポーネント追加と確認
- 02コンポーネントソースとバリアントの編集
- 03OKLCH変数によるテーマ設定
- 04cn()によるクラスの統合
- 05Fieldによるフォーム配置
活用シーン
- 01レジストリのコンポーネントを追加
- 02コンポーネントにバリアントや動作を追加
- 03ライト・ダークテーマの設定
- 04ダイアログ、タブ、コマンドメニューの構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-ui-shadcn-uiインストール後、Claude Code で/web-ui-shadcn-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/command-palette.mdcomposition.mdcore.mddata-table.mddialogs.mdforms.mdtheming.md詳細
shadcn/ui コンポーネントのパターン
クイックガイド: shadcn/ui は依存関係ではなく配布方式です。
npx shadcn@latest addを実行するとコンポーネントのソースがリポジトリに書き込まれ、その後は自分で管理します。これに伴い、ほかのすべてに関わる3つの点があります。カスタマイズは外部から上書きするのではなくファイルを編集して行うこと、アップグレードはバージョンを上げるのではなく差分を確認すること、そしてコンポーネントはプリミティブライブラリとユーティリティCSSを組み合わせた状態で提供され、ソースではそれらを名前でインポートすることです。OKLCH の CSS カスタムプロパティでテーマを設定し、cn()でクラスを統合し、Form/FormFieldパターンに代わるFieldでフィールドを配置します。
詳細なリソース:
- examples/core.md —
components.json、cn()、スケルトンローディング - examples/composition.md — コンポーネントの拡張、レスポンシブなダイアログ/ドロワー
- examples/forms.md —
Field、FieldGroup、FieldSet、従来のFormパターン - examples/dialogs.md — AlertDialog、Sheet、トースト
- examples/data-table.md — 並べ替え可能なヘッダーと行アクションを備えたテーブル
- examples/command-palette.md — キーボードショートカット付きのコマンドメニュー
- examples/theming.md — カスタム OKLCH カラー、ダークモード、テーマ対応コンポーネント
- reference.md — CLI コマンド、CSS 変数の表、アンチパターン、プラットフォームの変更点
適用するパス
- コンポーネントが使用するプリミティブライブラリ。
init --base radixと--base baseでは、同じレジストリエントリーでも異なるコンポーネントソースが生成されます。shadcn レベルの API(エクスポート名、cn()、バリアントの props、data-slot)はどちらでも同じなので、このスキルの内容はすべて当てはまります。異なるのは、ソースを編集するときに使用するプリミティブの props です。 - Field と従来の Form。 新しい作業では、レイアウトのみを担う
Fieldとその関連コンポーネントを使用します。すでにForm/FormField/FormItem/FormControl/FormMessageを使用しているコードベースでも引き続き動作しますが、これらは単一のフォームライブラリに結び付いているため、新しいフィールドには使用しません。どちらも examples/forms.md で説明しています。
<critical_requirements>
shadcn/ui のコードを書く前に
npx shadcn@latest add <name> でコンポーネントを追加します。 CLI はレジストリエントリの依存関係を解決し、必要なプリミティブパッケージをインストールして、components.json に記録されたパスにファイルを書き込み、インポートを設定済みのエイリアスに書き換えます。ドキュメントからソースを貼り付けても、これらの処理は行われません。
動作を変える前に、コンポーネントのソースを読みます。 それはパッケージ境界の内側ではなく、リポジトリ内のファイルです。そのため、「これを変更できるか」の答えは常に「できます」であり、考えるべき問いは「ほかに何がこれをインポートしているか」です。
すべてのクラスを cn() 経由で指定し、受け取った className は最後に渡します。 cn() は競合する Tailwind ユーティリティのうち最後のものを残します。これにより、呼び出し側の px-8 がコンポーネントの px-4 と詳細度で競合するのではなく、置き換えられます。
新しい背景色には必ず前景色を組み合わせます。 --brand に --brand-foreground がなければ、その表面上のテキストは直前の設定を引き継ぎます。通常、これは一方のテーマでは問題なくても、もう一方ではコントラストの問題になります。:root と .dark の両方にこのペアを追加し、ユーティリティに変換する @theme inline のマッピングも追加します。
単発のクラスよりバリアントを優先します。 複数の呼び出し側で必要なスタイルは、名前と型を持つコンポーネントのバリアントマップに追加します。呼び出し箇所でクラスリストを繰り返すと、同じ判断をその都度行うことになります。
</critical_requirements>
自動検出: shadcn/ui、shadcn、components.json、npx shadcn、shadcn@latest add、cn()、
data-slot、@theme inline、--base radix、--base base、Field、FieldLabel、FieldDescription、
FieldError、FieldGroup、FieldSet、FieldLegend、CommandDialog、SheetContent、AlertDialogAction、
--primary-foreground、--sidebar-*、--chart-*
対象:
- コンポーネントをプロジェクトに追加し、最新の状態に保つこと — CLI、
components.json、差分 - 管理しているコンポーネントのソースを編集すること:バリアント、プロパティ、動作の追加
- テーマの仕様 — 背景色と前景色のペア、OKLCH 値、
@theme inline、ダークモード - レジストリが提供する複合コンポーネントの組み合わせ:Card、Dialog、Sheet、Tabs、Command、Field
- 特定の操作に対して、レジストリ内の重複するコンポーネントから選択すること
別の箇所で扱う内容:
- ユーティリティクラスの記述と CSS パイプライン — このスキルではコンポーネントが読み取る変数を決めます。ユーティリティレイヤーの設定とビルド方法は別の内容です
- その下層にあるプリミティブライブラリ — 独自のプロパティ、
asChildのセマンティクス、アクセシビリティの仕様は、プロジェクトが選択したプリミティブライブラリの範囲です - 一般的な手法としてのバリアントマップの記述 — このスキルでは、コンポーネントのソースに含まれるバリアントマップの拡張を扱います
- フォームの状態、検証、送信 —
Fieldはレイアウトとアクセシビリティの連携を担い、値は保持しません
shadcn/ui は一般的な取引を逆転させます。コンポーネントライブラリは API を提供し、ソースを保持します。こちらはソースを提供し、何も保持しません。レジストリは出発点であり、ファイルが配置された瞬間に上流ではなくなります。
だからこそ、CLI は見た目以上に重要です。コピー&ペーストを便利にするだけのラッパーではありません。レジストリエントリの依存関係グラフ、エイリアス設定、選択したプリミティブのベースを把握している唯一のものです。また、後から --diff でコピーがレジストリからどれだけ乖離したかを確認できるのも、CLI があるからです。
コンポーネントを構成する要素は、構成上の事実としてここで名前を挙げています。 CLI が書き込むソースはプリミティブライブラリをインポートし、ユーティリティクラスを適用し、バリアントマップを宣言します。これらに言及しないスキルでは、読者が開いているファイルを説明できません。これらの構成要素を教えるのは別の役割であり、上の 別の箇所で扱う内容 に記載されています。
<decision_framework>
オーバーレイの選び方
Confirmation the user must answer?
└─ AlertDialog — no dismiss path except Cancel or Action
Form or detailed content?
├─ Wide viewport → Dialog (centred)
└─ Narrow viewport → Drawer (rises from the bottom)
Both at once → branch on a media query; see examples/composition.md
Editing in context, page still visible?
└─ Sheet — slides in from an edge
A short action or a single selection?
└─ Popover for content, DropdownMenu for a list of actions
フィールドコントロールの選び方
Text → Input, or Textarea when it wraps
Two to five options, all worth showing → RadioGroup
Many options → Select, or Combobox when it needs filtering
Several at once → Checkbox per option
A setting that takes effect immediately → Switch
An agreement or a term to accept → Checkbox
A date → Calendar, or DatePicker with a trigger
Whatever the control, wrap it in Field — not the legacy FormField.
</decision_framework>
コアパターン
パターン 1:コンポーネントのインストールと確認
CLI はレジストリへのインターフェースです。--dry-run、--diff、--view を使えば、コンポーネントについて何も書き込まずに確認できるため、アップグレードのレビューが可能になります。
npx shadcn@latest init # writes components.json
npx shadcn@latest add button card dialog
npx shadcn@latest add button --diff # how far your copy has drifted
npx shadcn@latest info # the resolved project context
コマンドの一覧は、reference.md を参照してください。
パターン 2:テーマの契約
各色は、背景色とその上に配置するテキスト色のペアです。:root で宣言し、.dark で上書きして、@theme inline を通じてユーティリティとして公開します。新しい色を追加するには、この 3 つすべてが必要です。そうしないと、どちらか一方のテーマにしか存在しないか、ユーティリティに存在しない状態になります。
:root {
--brand: oklch(0.627 0.265 303.9);
--brand-foreground: oklch(1 0 0);
}
@theme inline {
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
}
--brand-foreground は、--brand 上で使うテキスト色であり、ブランドカラーのテキスト色ではありません。命名が一般的な規則とは逆向きなので、取り違えるとテキストが見えなくなります。
コード全文:examples/theming.md
パターン 3:cn() と順序が重要な理由
cn() は連結ではなく、競合を解決する関数です。同じグループの 2 つのユーティリティは後に指定したものが残るため、渡された className を最後に置くことで、呼び出し側の上書きが実際に優先されます。
cn("px-4", "px-8"); // → "px-8", not "px-4 px-8"
<div className={cn("rounded-lg border bg-card shadow-sm", className)} />;
単純な文字列連結では両方のクラスがリストに残り、どちらが適用されるかは呼び出し箇所ではなく、生成されたスタイルシート内の順序によって決まります。
コード全文:examples/core.md
パターン 4:バリアントマップを通じたコンポーネントの拡張
各コンポーネントは、バリアントマップとしてスタイルを宣言しています。これは、ベースとなるクラスのリストに、名前付きの variant と size の軸を加えたもので、プロパティの型はこのマップから導出されます。独自のソースでこのマップを編集すれば、バリアントを追加できます。拡張 API はありませんが、必要ないためです。
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
brand: "bg-brand text-brand-foreground hover:bg-brand/90", // added
},
}
<Button variant="brand">Subscribe</Button>;
動作を制御するプロパティの追加も同じ方法です。examples/composition.md のローディングボタンを参照してください。
パターン 5:フォームレイアウトのための Field
Field とその関連コンポーネントは、ラベル、説明、エラー用スロット、およびそれらの間の ARIA の連携を担いますが、値は保持しません。そのため、どのフォームライブラリやサーバーアクションとも、あるいは何も使わなくても連携できます。
<Field data-invalid={hasError}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" aria-invalid={hasError} />
<FieldDescription>We will never share your email.</FieldDescription>
{hasError && <FieldError errors={errors} />}
</Field>
契約となるのは、次の 3 点です。Field に data-invalid を設定すること、コントロールに aria-invalid を設定すること、そしてエラーがある場合に限り FieldError をレンダリングすることです。FieldGroup、FieldSet、FieldLegend はフィールドをグループ化し、orientation はラベルの配置を切り替えます。
コード全文:examples/forms.md
パターン 6:複合コンポーネントの組み合わせ
Card、Dialog、Sheet、Tabs、Command は、それぞれ複数のパーツで構成されています。用途に合わせてラップして使ってください。パーツを通常の要素に置き換えると、スタイル用のフックが失われ、オーバーレイの場合は動作も失われます。
function ProductCard({ title, price }: ProductCardProps) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>${price}</CardDescription>
</CardHeader>
</Card>
);
}
トリガーに asChild を指定すると、子要素の内側にネストするのではなく、その子要素に統合されます。これにより、リンクをラップした Button がボタンの内側にアンカーをレンダリングするのを防げます。
コード全文:examples/dialogs.md、examples/command-palette.md
<red_flags>
注意点
実行時に問題が発生します:
components.jsonがありません。エイリアス、スタイル、プリミティブのベースが記録されているため、すべての CLI コマンドが失敗します- 背景色を追加したのに対応する
-foregroundの組み合わせがありません。その面上のテキストは継承され、通常、一方のテーマでは問題なく表示されても、もう一方のテーマではコントラストが不足します :rootに色を追加したものの、.darkに追加していない、または@theme inlineでマッピングしていません。一方のテーマにしか存在しないか、ユーティリティにはまったく反映されません- すでに
oklch(...)である値をhsl()で囲んでいます。無効な色のため宣言が破棄され、要素は継承した色にフォールバックします Buttonの中にリンクを入れ、リンクにasChildを指定していません。ボタンの中にアンカーを入れるのは無効な HTML であり、キーボードユーザーには分かりにくい単一のコントロールとして認識されますSelectにvalueもdefaultValueも指定せず、onValueChangeを設定しています。選択内容が表示されません
意外な動作:
--primary-foregroundは--primary上のテキスト色であり、プライマリカラーのテキスト色ではありませんcn()を使わずにクラス文字列を連結すると、競合するユーティリティが両方残ります。そのため、どちらが優先されるかは呼び出し元ではなくスタイルシートの順序によって決まりますcomponents/ui/内のコンポーネントを編集するのが想定されたワークフローです。後からレジストリとの差分を確認するには--diffを使います- React 19 では、同梱のコンポーネントは
refを通常のプロパティとして受け取り、対象とするクラス名をエクスポートするのではなく、各パーツにdata-slotを指定します - システム設定を読み取るテーマプロバイダーでは、
<html>にsuppressHydrationWarningが必要です。サーバーはクライアントがどのクラスを適用するか分からないためです Form/FormField/FormItem/FormControl/FormMessageは引き続き使用でき、引き続き特定のフォームライブラリに紐付いています。新しいフィールドはFieldに追加します- チャートの色は
var(--chart-1)として直接読み取られます。以前のセットアップで使われていたhsl()によるラップは、単に冗長なのではなく、現在では誤りです