shadcn-inertia — Claude Code skill: what it does, how to install
Adapts shadcn/ui components for Inertia.js Rails React applications instead of Next.js. Guides form wiring, dialogs, and dark mode without react-hook-form.
npx skills add inertia-rails/skills --skill shadcn-inertia -a claude-codeWhat shadcn-inertia does
The skill provides integration rules for using shadcn/ui in an Inertia Rails React stack. Standard shadcn examples assume Next.js runtime features and fail in Inertia. It strips out 'use client' directives, drops Server Component assumptions, and swaps Next.js navigation components for Inertia equivalents like <Link>, <Head>, and router.
Forms rely directly on the Inertia <Form> component paired with plain input elements using the name attribute. Standard shadcn wrappers like FormField and FormMessage crash Inertia apps because they internally depend on useFormContext from react-hook-form. Validation messages must render directly from the Inertia errors map.
Dark mode configuration replaces next-themes with a small inline script placed in Rails application.html.erb to prevent flash of wrong theme before React hydrates. The alias @/ belongs only in tsconfig.json, as vite-plugin-ruby already provides path resolution in vite.config.ts.
What it helps with
- adapt shadcn/ui components for Inertia.js and Ruby on Rails
- convert Next.js shadcn form examples to native Inertia <Form>
- prevent dark mode theme flicker without using next-themes
- wire server-side table sorting using Inertia router visits
Example prompts for the agent
- Convert this shadcn modal dialog to work with Inertia router navigation
- Rewrite this shadcn form example for Inertia without react-hook-form
- Set up shadcn dark mode in Rails without next-themes
How to install shadcn-inertia
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill shadcn-inertia -a claude-code2Manually: put the files in place
~/.claude/skills/shadcn-inertia/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/shadcn-inertia/
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill shadcn-inertia -a codex2Manually: put the files in place
.agents/skills/shadcn-inertia/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill shadcn-inertia -a cursor2Manually: put the files in place
.cursor/skills/shadcn-inertia/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install inertia-rails/skills shadcn-inertia2Manually: put the files in place
.github/skills/shadcn-inertia/SKILL.md1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill shadcn-inertia -a gemini-cli2Platform CLI
gemini skills install https://github.com/inertia-rails/skills --path skills/shadcn-inertiaManually: put the files in place
.devin/skills/shadcn-inertia/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill shadcn-inertia -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about shadcn-inertia
How do I install shadcn-inertia in Claude Code?
Run npx skills add inertia-rails/skills --skill shadcn-inertia -a claude-code. The skill instructions will be placed in ~/.claude/skills/shadcn-inertia/SKILL.md.
What does the shadcn-inertia skill do?
It teaches coding assistants how to adapt shadcn/ui patterns for Inertia Rails React. It prevents Next.js assumptions like react-hook-form, server components, and next-themes from breaking your setup.
Why must shadcn FormField components be removed?
Components like FormField and FormMessage rely on useFormContext from react-hook-form. Without that library, they fail during rendering, so Inertia forms require plain inputs with name props instead.
Source text
SKILL.md · MIT
---
name: shadcn-inertia
description: >-
shadcn/ui component integration for Inertia Rails React (NOT Next.js): forms, dialogs, tables, toasts, dark mode,
command palette, and more. Use when building UI with shadcn/ui components in an Inertia app or adapting shadcn
examples from Next.js. NEVER react-hook-form/zod — wire shadcn inputs to Inertia Form via name attribute. Flash
toasts require Rails flash_keys initializer config.
---
# 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 `<Form>` + `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 `<Link>`, `<Head>`, `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 `<Form>` component |
| `FormField`, `FormItem`, `FormMessage` | Plain `<Input name="...">` + `errors.field` |
| `next-themes` | CSS class strategy + `@custom-variant` |
| `useRouter()` (Next) | `router` from `@inertiajs/react` |
| `next/link` | `<Link>` from `@inertiajs/react` |
| `next/head` | `<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 `<Form>`,
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 `<Form>`
Use plain shadcn `Input`/`Label`/`Button` with `name` attributes inside Inertia `<Form>`.
See `inertia-rails-forms` skill for full `<Form>` 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):
<Label htmlFor="name">Name</Label>
<Input id="name" name="name" />
{errors.name && <p className="text-sm text-destructive">{errors.name}</p>}
```
**`<Select>` requires `name` prop** for Inertia `<Form>` integration — shadcn examples
omit it because react-hook-form manages values differently:
```tsx
<Select name="role" defaultValue="member">
<SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
```
## 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 (
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!isOpen) {
router.replaceProp('show_dialog', false)
}
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{user.name}</DialogTitle>
</DialogHeader>
{/* content */}
</DialogContent>
</Dialog>
)
}
```
## Table with Server-Side Sorting
shadcn `<Table>` renders normally. The Inertia-specific part is sorting via `router.get`:
```tsx
const handleSort = (column: string) => {
router.get('/users', { sort: column }, { preserveState: true })
}
<TableHead onClick={() => handleSort('name')} className="cursor-pointer">
Name {sort === 'name' && '↑'}
</TableHead>
```
Use `<Link>` (not `<a>`) 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 `<head>` (before React
hydrates) and call `initializeTheme()` in your Inertia entrypoint:
```erb
<%# 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>
```
```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
`<html>` — 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 `<Select>` — shadcn examples omit it |
| Dialog closes unexpectedly | Missing or wrong `onOpenChange` handler | Use `onOpenChange={(open) => { if (!open) closeHandler() }}` |
| Flash of wrong theme (FOUC) | Missing inline `<script>` in `<head>` | Add dark mode script before stylesheets (see Dark Mode section) |
## Related Skills
- **Form component** → `inertia-rails-forms` (`<Form>` render function, useForm)
- **Flash config** → `inertia-rails-controllers` (flash_keys initializer)
- **Flash access** → `inertia-rails-pages` (usePage().flash)
- **URL-driven dialogs** → `inertia-rails-pages` (router.get pattern)
## References
Load [`references/components.md`](references/components.md) (~300 lines) when building
shadcn components beyond those shown above (Accordion, Sheet, Tabs, DropdownMenu,
AlertDialog with Inertia patterns).
**Do NOT load** `components.md` for basic Form, Select, Dialog, or Table usage —
the examples above are sufficient.
© inertia-rails · MIT · inertia-rails/skills