--- id: inertia-rails/skills/shadcn-inertia version: "b4b1e944" license: MIT install: manual updated: 2026-02-13 --- # shadcn-inertia — shadcn-inertia teaches you how to use shadcn/ui components in Inertia Rails React applications by adapting Next.js examples. It replaces shadcn's form components and react-hook-form with Inertia's Form component, removes Next.js directives and APIs, and covers dialogs, tables, toasts, and dark mode setup. Publisher: inertia-rails · Stars: 64 · Updated: 2026-02-13 Install (manual): `git clone https://github.com/inertia-rails/skills` ## SKILL.md # shadcn/ui for Inertia Rails shadcn/ui patterns adapted for Inertia.js + Rails + React. NOT Next.js. **Before using a shadcn example, ask:** - **Does it use `react-hook-form` + `zod`?** → Replace with Inertia `
` + `name` attributes. Inertia handles CSRF, errors, redirects, processing state — react-hook-form would fight all of this. - **Does it use `'use client'`?** → Remove it. Inertia has no RSC — all components are client components. - **Does it use `next/link`, `next/head`, `useRouter()`?** → Replace with Inertia ``, ``, `router`. ## Key Differences from Next.js Defaults | shadcn default (Next.js) | Inertia equivalent | |---|---| | `'use client'` directive | Remove — not needed (no RSC) | | `react-hook-form` + `zod` | Inertia `` component | | `FormField`, `FormItem`, `FormMessage` | Plain `` + `errors.field` | | `next-themes` | CSS class strategy + `@custom-variant` | | `useRouter()` (Next) | `router` from `@inertiajs/react` | | `next/link` | `` from `@inertiajs/react` | | `next/head` | `` from `@inertiajs/react` | **NEVER use shadcn's `FormField`, `FormItem`, `FormLabel`, `FormMessage` components** — they depend on react-hook-form's `useFormContext` internally and will crash without it. Use plain shadcn `Input`/`Label`/`Select` with `name` attributes inside Inertia ``, and render errors from the render function's `errors` object (see examples below). ## Setup `npx shadcn@latest init`. add `@/` resolve aliases to `tsconfig.json` if not present, **Do NOT add `@/` resolve aliases to `vite.config.ts`** — `vite-plugin-ruby` already provides them. ## shadcn Inputs in Inertia `` Use plain shadcn `Input`/`Label`/`Button` with `name` attributes inside Inertia ``. See `inertia-rails-forms` skill for full `` API — this section covers shadcn-specific adaptation only. **The key pattern:** Replace shadcn's `FormField`/`FormItem`/`FormMessage` with plain components + manual error display: ```tsx // shadcn error display pattern (replaces FormMessage): {errors.name &&

{errors.name}

} ``` **` Admin Member ``` ## Dialog with Inertia Navigation ```tsx import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { router } from '@inertiajs/react' function UserDialog({ open, user }: { open: boolean; user: User }) { return ( { if (!isOpen) { router.replaceProp('show_dialog', false) } }} > {user.name} {/* content */} ) } ``` ## Table with Server-Side Sorting shadcn `` renders normally. The Inertia-specific part is sorting via `router.get`: ```tsx const handleSort = (column: string) => { router.get('/users', { sort: column }, { preserveState: true }) } handleSort('name')} className="cursor-pointer"> Name {sort === 'name' && '↑'} ``` Use `` (not ``) for row links to preserve SPA navigation. ## Toast with Flash Messages Flash config (`flash_keys`) is in `inertia-rails-controllers`. Flash access (`usePage().flash`) is in `inertia-rails-pages`. This section covers **toast UI wiring only**. **MANDATORY — READ ENTIRE FILE** when implementing flash-based toasts with Sonner: [`references/flash-toast.md`](references/flash-toast.md) (~80 lines) — full `useFlash` hook and Sonner toast provider. **Do NOT load** if only reading flash values without toast UI. Key gotcha: `flash_keys` in the Rails initializer MUST match your `FlashData` TypeScript type — do NOT use `success`/`error` unless you also update both. ## Dark Mode (No next-themes) `npx shadcn@latest init` generates CSS variables for light/dark and `@custom-variant dark (&:is(.dark *));` in your CSS (Tailwind v4). No extra setup needed for the variables themselves. **CRITICAL — prevent flash of wrong theme (FOUC):** Next.js handles this automatically; Inertia does NOT. Add an inline script in `` (before React hydrates) and call `initializeTheme()` in your Inertia entrypoint: ```erb <%# app/views/layouts/application.html.erb — in , before any stylesheets %> ``` ```tsx // app/frontend/entrypoints/inertia.tsx import { initializeTheme } from '@/hooks/use-appearance' initializeTheme() // must run before createInertiaApp ``` Use a `useAppearance` hook (light/dark/system modes, localStorage persistence, `matchMedia` listener) instead of `next-themes`. Toggle via `.dark` class on `` — no provider needed. ## Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | `FormField`/`FormMessage` crash | Using shadcn form components that depend on react-hook-form | Replace with plain `Input`/`Label` + `errors.field` display | | `Select` value not submitted | Missing `name` prop | Add `name="field"` to `