inertia-rails/skills

shadcn-inertia

>- 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.

First seen Feb 13, 2026

Installation

$ npx skills add inertia-rails/skills --skill shadcn-inertia

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from inertia-rails/skills · top by installs.

npx skills add inertia-rails/skills

Browse all from inertia-rails/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 67
License LICENSE
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,168 B
  • docs SUMMARY.md 412 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 389 installs

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 <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.tsvite-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:

// 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:

<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

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:

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:

<%# 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

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 componentinertia-rails-forms (<Form> render function, useForm)
  • Flash configinertia-rails-controllers (flash_keys initializer)
  • Flash accessinertia-rails-pages (usePage().flash)
  • URL-driven dialogsinertia-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.