重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
shadcn-inertia by inertia-rails/skills
npx skills add https://github.com/inertia-rails/skills --skill shadcn-inertia为 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 、、。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<Link><Head>router| 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 已经提供了它们。
<Form> 中的 shadcn 输入组件在 Inertia <Form> 内部使用带有 name 属性的普通 shadcn Input/Label/Button。完整的 <Form> API 请参见 inertia-rails-forms 技能——本节仅涵盖与 shadcn 相关的适配。
关键模式: 用普通组件 + 手动错误显示替换 shadcn 的 FormField/FormItem/FormMessage:
// shadcn 错误显示模式(替换 FormMessage):
<Label htmlFor="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="选择角色" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">管理员</SelectItem>
<SelectItem value="member">成员</SelectItem>
</SelectContent>
</Select>
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>
{/* 内容 */}
</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">
名称 {sort === 'name' && '↑'}
</TableHead>
对于行链接,使用 <Link>(而非 <a>)以保持 SPA 导航。
Flash 配置(flash_keys)在 inertia-rails-controllers 中。Flash 访问(usePage().flash)在 inertia-rails-pages 中。本节仅涵盖 toast UI 的连接。
强制要求——实现基于 flash 的 Sonner toast 时,请完整阅读整个文件: references/flash-toast.md(约 80 行)——完整的 useFlash 钩子和 Sonner toast 提供者。如果仅读取 flash 值而不使用 toast UI,请勿加载。
关键陷阱:Rails 初始化器中的 flash_keys 必须与你的 FlashData TypeScript 类型匹配——除非你也更新了这两者,否则不要使用 success/error。
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 — 在 <head> 中,在任何样式表之前 %>
<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() // 必须在 createInertiaApp 之前运行
使用 useAppearance 钩子(浅色/深色/系统模式、localStorage 持久化、matchMedia 监听器)代替 next-themes。通过 <html> 上的 .dark 类进行切换——不需要提供者。
| 症状 | 原因 | 修复方法 |
|---|---|---|
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)inertia-rails-controllers(flash_keys 初始化器)inertia-rails-pages(usePage().flash)inertia-rails-pages(router.get 模式)构建超出上述示例的 shadcn 组件(Accordion、Sheet、Tabs、DropdownMenu、采用 Inertia 模式的 AlertDialog)时,请加载 references/components.md(约 300 行)。
对于基本的 Form、Select、Dialog 或 Table 使用,请勿加载 components.md——上述示例已足够。
每周安装量
57
仓库
GitHub 星标数
35
首次出现
2026年2月13日
安全审计
安装于
opencode56
codex56
gemini-cli55
github-copilot54
amp53
kimi-cli52
shadcn/ui patterns adapted for Inertia.js + Rails + React. NOT Next.js.
Before using a shadcn example, ask:
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.'use client'? → Remove it. Inertia has no RSC — all components are client components.next/link, next/head, useRouter()? → Replace with Inertia <Link>, <Head>, router.| 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="..."> + |
NEVER use shadcn'sFormField, 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).
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.
<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>
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> 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.
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.
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.
| 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 — shadcn examples omit it |
inertia-rails-forms (<Form> render function, useForm)inertia-rails-controllers (flash_keys initializer)inertia-rails-pages (usePage().flash)inertia-rails-pages (router.get pattern)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.
Weekly Installs
57
Repository
GitHub Stars
35
First Seen
Feb 13, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode56
codex56
gemini-cli55
github-copilot54
amp53
kimi-cli52
Tailwind CSS v4 + shadcn/ui 生产级技术栈配置指南与最佳实践
2,600 周安装
errors.fieldnext-themes | CSS class strategy + @custom-variant |
useRouter() (Next) | router from @inertiajs/react |
next/link | <Link> from @inertiajs/react |
next/head | <Head> from @inertiajs/react |
<Select>| 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) |