shadcn-inertia について
shadcn-inertia は、shadcn/ui コンポーネントを Inertia.js、Rails、React のプロジェクトに適用するスキルです。Next.js や react-hook-form の慣例を Inertia のパターンに置き換える方法を、フォーム、ナビゲーション、フラッシュ通知、テーマ設定まで解説します。
主な機能
- 01Next.js のパターンを Inertia 用に変換
- 02Inertia フォームフィールドの指針
- 03SPA ナビゲーションと URL ダイアログ
- 04フラッシュ通知とトーストの連携
- 05next-themes を使わないダークモード設定
活用シーン
- 01shadcn フォームを Inertia 向けに適応
- 02サーバー側テーブルに並べ替えを追加
- 03フラッシュ通知を Sonner トーストに接続
- 04ダークモード切り替え時のちらつきを防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ inertia-rails/ skills --skill shadcn-inertiaインストール後、Claude Code で/shadcn-inertiaを入力して読み込まれたか確認します。
同梱ファイルreferences/components.mdreferences/flash-toast.md
詳細
Inertia Rails 向け shadcn/ui
Inertia.js + Rails + React 向けに適応した shadcn/ui のパターンです。Next.js 向けではありません。
shadcn の例を使う前に、次の点を確認してください。
react-hook-form+zodを使用していますか? → Inertia の<Form>+name属性に置き換えてください。Inertia は CSRF、エラー、リダイレクト、処理中の状態を管理します。react-hook-form を使うと、これらと競合します。'use client'を使用していますか? → 削除してください。Inertia には RSC がないため、すべてのコンポーネントがクライアントコンポーネントです。next/link、next/head、useRouter()を使用していますか? → Inertia の<Link>、<Head>、routerに置き換えてください。
Next.js のデフォルト設定との主な違い
| shadcn のデフォルト(Next.js) | Inertia での対応 |
|---|---|
'use client' ディレクティブ | 削除します(RSC がないため不要です) |
react-hook-form + zod | Inertia の <Form> コンポーネント |
FormField、FormItem、FormMessage | 通常の <Input name="..."> + errors.field |
next-themes | CSS クラス方式 + @custom-variant |
useRouter()(Next) | @inertiajs/react の router |
next/link | @inertiajs/react の <Link> |
next/head | @inertiajs/react の <Head> |
shadcn の FormField、FormItem、FormLabel、FormMessage コンポーネントは絶対に使用しないでください —
内部で react-hook-form の useFormContext に依存しているため、それなしではクラッシュします。
Inertia の <Form> 内では、name 属性を指定した通常の shadcn Input/Label/Select を使い、
レンダー関数の errors オブジェクトからエラーを表示してください(以下の例を参照)。
セットアップ
npx shadcn@latest init を実行します。存在しない場合は、tsconfig.json に @/ の解決エイリアスを追加してください。
vite.config.ts に @/ の解決エイリアスを追加しないでください — vite-plugin-ruby がすでに提供しています。
Inertia の <Form> 内での shadcn 入力コンポーネント
Inertia の <Form> 内では、name 属性を指定した通常の shadcn Input/Label/Button を使用してください。
<Form> API の全容については inertia-rails-forms スキルを参照してください。このセクションでは、shadcn に固有の適応方法のみを説明します。
重要なパターン: shadcn の FormField/FormItem/FormMessage を、通常の
コンポーネント + 手動でのエラー表示に置き換えます。
// shadcn error display pattern (replaces FormMessage):
<Label htmlFor="name">Name</Label>
<Input id="name" name="name" />
{errors.name && <p className="text-sm text-destructive">{errors.name}</p>}
Inertia の <Form> と連携させるには、<Select> に name prop が必要です。shadcn の例では、
react-hook-form が異なる方法で値を管理するため、省略されています。
<Select name="role" defaultValue="member">
<SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
Inertia ナビゲーションを使ったダイアログ
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { router } from '@inertiajs/react'
function UserDialog({ open, user }: { open: boolean; user: User }) {
return (
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!isOpen) {
router.replaceProp('show_dialog', false)
}
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{user.name}</DialogTitle>
</DialogHeader>
{/* content */}
</DialogContent>
</Dialog>
)
}
サーバーサイドソートを使ったテーブル
shadcn の <Table> は通常どおりレンダーされます。Inertia 固有の部分は、router.get によるソートです。
const handleSort = (column: string) => {
router.get('/users', { sort: column }, { preserveState: true })
}
<TableHead onClick={() => handleSort('name')} className="cursor-pointer">
Name {sort === 'name' && '↑'}
</TableHead>
SPA ナビゲーションを維持するため、行リンクには <a> ではなく <Link> を使用してください。
フラッシュメッセージを使ったトースト
フラッシュの設定(flash_keys)は inertia-rails-controllers にあります。フラッシュへのアクセス(usePage().flash)は inertia-rails-pages にあります。このセクションでは、トースト UI の接続のみを扱います。
必須 — Sonner を使ったフラッシュベースのトーストを実装する場合は、ファイル全体を読んでください。
references/flash-toast.md(約80行)— useFlash フックと Sonner トーストプロバイダーの全容です。トースト UI を使わずにフラッシュ値を読み取るだけの場合は、読み込まないでください。
重要な注意点:Rails イニシャライザーの flash_keys は、TypeScript の FlashData
型と一致させる必要があります。両方も更新しない限り、success/error は使用しないでください。
ダークモード(next-themes なし)
npx shadcn@latest init は、ライト/ダーク用の CSS 変数と、CSS(Tailwind v4)内の
@custom-variant dark (&:is(.dark *)); を生成します。CSS 変数自体に追加の設定は不要です。
重要 — テーマの誤表示によるちらつき(FOUC)を防止する: Next.js では自動的に処理されますが、Inertia では処理されません。<head> 内(React の
ハイドレーション前)にインラインスクリプトを追加し、Inertia のエントリーポイントで initializeTheme() を呼び出してください。
<%# app/views/layouts/application.html.erb — in <head>, before any stylesheets %>
<script>
document.documentElement.classList.toggle(
"dark",
localStorage.appearance === "dark" ||
(!("appearance" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),
);
</script>
// app/frontend/entrypoints/inertia.tsx
import { initializeTheme } from '@/hooks/use-appearance'
initializeTheme() // must run before createInertiaApp
next-themes の代わりに、useAppearance フック(ライト/ダーク/システムのモード、localStorage への永続化、
matchMedia リスナー)を使用してください。<html> の .dark クラスで切り替えます — プロバイダーは不要です。
トラブルシューティング
| 症状 | 原因 | 対処方法 |
|---|---|---|
FormField/FormMessage がクラッシュする | react-hook-form に依存する shadcn のフォームコンポーネントを使用している | 通常の Input/Label と errors.field の表示に置き換える |
Select の値が送信されない | name プロパティがない | <Select> に name="field" を追加する — shadcn の例では省略されています |
| ダイアログが予期せず閉じる | onOpenChange ハンドラーがない、または正しくない | onOpenChange={(open) => { if (!open) closeHandler() }} を使用する |
| 間違ったテーマが一瞬表示される(FOUC) | <head> にインラインの <script> がない | スタイルシートより前にダークモード用スクリプトを追加する(「ダークモード」セクションを参照) |
関連スキル
- フォームコンポーネント →
inertia-rails-forms(<Form>のレンダー関数、useForm) - フラッシュ設定 →
inertia-rails-controllers(flash_keys イニシャライザー) - フラッシュへのアクセス →
inertia-rails-pages(usePage().flash) - URL 駆動型ダイアログ →
inertia-rails-pages(router.get パターン)
参考資料
上記に示したもの以外の shadcn コンポーネント(Accordion、Sheet、Tabs、DropdownMenu、
Inertia のパターンを使った AlertDialog)を構築する場合は、references/components.md(約 300 行)を読み込んでください。
基本的な Form、Select、Dialog、Table の使用では、components.md を読み込まないでください —
上記の例で十分です。