shadcn-inertia

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

shadcn/ui for Inertia Rails

适用于Inertia Rails的shadcn/ui

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

Key Differences from Next.js Defaults

与Next.js默认配置的主要差异

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).
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>
内部使用普通的shadcn
Input
/
Label
/
Select
组件并添加
name
属性, 通过渲染函数的
errors
对象展示错误(见下方示例)。

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.
执行
npx shadcn@latest init
。如果
tsconfig.json
中没有
@/
解析别名,请添加, 请勿在
vite.config.ts
中添加
@/
解析别名
——
vite-plugin-ruby
已自动提供该配置。

shadcn Inputs in Inertia
<Form>

在Inertia
<Form>
中使用shadcn输入组件

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>
在Inertia
<Form>
内部使用普通的shadcn
Input
/
Label
/
Button
组件并添加
name
属性。 完整的
<Form>
API请参考
inertia-rails-forms
技能——本节仅介绍shadcn相关的适配内容。
核心模式: 用普通组件+手动错误展示替代shadcn的
FormField
/
FormItem
/
FormMessage
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>
组件必须添加
name
属性
才能与Inertia
<Form>
集成——shadcn示例中省略了该属性,因为react-hook-form对值的管理方式不同:
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

结合Inertia导航的对话框

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>
  )
}
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.
shadcn
<Table>
可正常渲染。Inertia相关的部分是通过
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>
请使用
<Link>
(而非
<a>
)作为行链接,以保留SPA导航体验。

Toast with Flash Messages

结合Flash消息的提示框

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
(~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.
Flash配置(
flash_keys
)在
inertia-rails-controllers
中。Flash访问 (
usePage().flash
)在
inertia-rails-pages
中。本节仅介绍提示框UI的关联配置
必须操作——实现基于Flash的Sonner提示框时请完整阅读文件
references/flash-toast.md
(约80行)——包含完整的
useFlash
钩子和Sonner提示框提供者。如果仅读取Flash值而不使用提示框UI,请不要加载该文件
关键注意事项:Rails初始化器中的
flash_keys
必须与你的
FlashData
TypeScript类型匹配——除非同时更新两者,否则不要使用
success
/
error

Dark Mode (No next-themes)

深色模式(无需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.
npx shadcn@latest init
会为浅色/深色模式生成CSS变量, 并在你的CSS中添加
@custom-variant dark (&:is(.dark *));
(适用于Tailwind v4)。变量本身无需额外配置。
重要——避免主题闪烁(FOUC):Next.js会自动处理该问题;但Inertia不会。请在
<head>
中添加内联脚本(在React水合之前),并在Inertia入口文件中调用
initializeTheme()
erb
<%# app/views/layouts/application.html.erb — 在<head>中,所有样式表之前 %>
<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() // 必须在createInertiaApp之前调用
请使用
useAppearance
钩子(支持浅色/深色/系统模式、本地存储持久化、
matchMedia
监听)替代
next-themes
。通过
<html>
元素的
.dark
类切换主题——无需提供者。

Troubleshooting

故障排查

SymptomCauseFix
FormField
/
FormMessage
crash
Using shadcn form components that depend on react-hook-formReplace 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 unexpectedlyMissing 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)
症状原因修复方案
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>
在样式表之前添加深色模式脚本(见深色模式章节)

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)
  • 表单组件
    inertia-rails-forms
    <Form>
    渲染函数、useForm)
  • Flash配置
    inertia-rails-controllers
    (flash_keys初始化器)
  • Flash访问
    inertia-rails-pages
    (usePage().flash)
  • URL驱动的对话框
    inertia-rails-pages
    (router.get模式)

References

参考资料

Load
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.
当构建上述示例之外的shadcn组件(如Accordion、Sheet、Tabs、DropdownMenu、 结合Inertia模式的AlertDialog)时,请加载
references/components.md
(约300行)。
如果仅使用基础的Form、Select、Dialog或Table,请不要加载
components.md
—— 上述示例已足够使用。