关于 shadcn-inertia
shadcn-inertia 是一项技能,用于将 shadcn/ui 组件适配到 Inertia.js、Rails 和 React 项目。它说明如何以 Inertia 模式替换 Next.js 和 react-hook-form 约定,涵盖表单、导航、闪消息提示及主题处理。
主要功能
- 01将 Next.js 模式映射为 Inertia 用法
- 02Inertia 表单字段指南
- 03单页导航与 URL 对话框
- 04闪消息提示接线方式
- 05不使用 next-themes 的暗色模式设置
适用场景
- 01将 shadcn 表单适配到 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 属性——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>
行链接请使用 <Link>(而不是 <a>),以保留 SPA 导航。
使用 Flash 消息显示 Toast
Flash 配置(flash_keys)位于 inertia-rails-controllers。Flash 访问(usePage().flash)
位于 inertia-rails-pages。本节仅介绍 Toast UI 的接线配置。
必须——实现基于 flash 的 Sonner Toast 时,请阅读整个文件:
references/flash-toast.md(约 80 行)——包含完整的 useFlash
hook 和 Sonner Toast provider。如果只是读取 flash 值,不涉及 Toast UI,则不要加载。
注意:Rails 初始化程序中的 flash_keys 必须与 TypeScript 类型 FlashData
匹配——除非同时更新两者,否则不要使用 success/error。
深色模式(不使用 next-themes)
npx shadcn@latest init 会生成浅色/深色模式的 CSS 变量,并在 CSS(Tailwind v4)中添加
@custom-variant dark (&:is(.dark *));。这些变量本身无需额外设置。
关键——防止错误主题闪烁(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
请使用 useAppearance hook(支持浅色/深色/系统模式、localStorage 持久化、
matchMedia 监听器),而不是 next-themes。通过 <html> 上的 .dark 类进行切换——无需 provider。
故障排查
| 现象 | 原因 | 修复方法 |
|---|---|---|
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) - Flash 配置 →
inertia-rails-controllers(flash_keys 初始化器) - Flash 访问 →
inertia-rails-pages(usePage().flash) - 由 URL 驱动的对话框 →
inertia-rails-pages(router.get 模式)
参考资料
构建上述示例未涵盖的 shadcn 组件(Accordion、Sheet、Tabs、DropdownMenu、
带有 Inertia 模式的 AlertDialog)时,加载 references/components.md(约 300 行)。
不要加载 components.md 来了解基本的 Form、Select、Dialog 或 Table 用法 —
上述示例已足够。