shadcn-vue-inertia
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseshadcn-vue for Inertia Rails
适用于Inertia Rails的shadcn-vue
shadcn-vue patterns adapted for Inertia.js + Rails + Vue 3. NOT Nuxt.
Before using a shadcn-vue example, ask:
- Does it use Nuxt-specific APIs? (,
useRouter,useFetch) → Replace with Inertia<NuxtLink>, server props,router<Link> - Does it use +
vee-validate? → Replace with Inertiazod+<Form>attributes. Inertia handles CSRF, errors, redirects, processing state.name
为Inertia.js + Rails + Vue 3适配的shadcn-vue模式。不适用于Nuxt。
在使用shadcn-vue示例前,请确认:
- 是否使用了Nuxt专属API?(、
useRouter、useFetch)→ 替换为Inertia的<NuxtLink>、服务端props、router<Link> - 是否使用了+
vee-validate? → 替换为Inertia的zod+<Form>属性。Inertia会处理CSRF、错误、重定向、处理状态。name
Key Differences from Nuxt Defaults
与Nuxt默认配置的主要差异
| shadcn-vue default (Nuxt) | Inertia equivalent |
|---|---|
| Server-rendered props via controller |
| |
| |
| Inertia |
| Plain |
| |
NEVER use shadcn-vue's , , , components —
they depend on vee-validate's form context internally and will crash without it.
Use plain shadcn-vue // with attributes inside Inertia ,
and render errors from the scoped slot's object.
FormFieldFormItemFormLabelFormMessageInputLabelSelectname<Form>errors| shadcn-vue默认配置(Nuxt) | Inertia等效方案 |
|---|---|
| 通过控制器获取服务端渲染的props |
| 来自 |
| 来自 |
| Inertia的 |
| 普通 |
| 来自 |
请勿使用shadcn-vue的、、、组件 ——
它们内部依赖vee-validate的表单上下文,没有该依赖会崩溃。
在Inertia 中使用带属性的普通shadcn-vue //组件,
并通过作用域插槽的对象渲染错误信息。
FormFieldFormItemFormLabelFormMessage<Form>nameInputLabelSelecterrorsSetup
配置步骤
npx shadcn-vue@latest init@/tsconfig.json@/vite.config.tsvite-plugin-ruby执行。如果中没有解析别名,请添加。
请勿在中添加解析别名 —— 已提供该配置。
npx shadcn-vue@latest inittsconfig.json@/vite.config.ts@/vite-plugin-rubyshadcn-vue Inputs in Inertia <Form>
<Form>shadcn-vue输入组件与Inertia <Form>
的集成
<Form>Use plain shadcn-vue // with attributes inside Inertia .
See skill (+ ) for full API.
InputLabelButtonname<Form>inertia-rails-formsreferences/vue.md<Form>The key pattern: Replace shadcn-vue's // with plain
components + manual error display:
FormFieldFormItemFormMessagevue
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
</script>
<template>
<Form method="post" action="/users">
<template #default="{ errors, processing }">
<div class="space-y-4">
<div>
<Label for="name">Name</Label>
<Input id="name" name="name" />
<p v-if="errors.name" class="text-sm text-destructive">{{ errors.name }}</p>
</div>
<div>
<Label for="email">Email</Label>
<Input id="email" name="email" type="email" />
<p v-if="errors.email" class="text-sm text-destructive">{{ errors.email }}</p>
</div>
<Button type="submit" :disabled="processing">
{{ processing ? 'Creating...' : 'Create User' }}
</Button>
</div>
</template>
</Form>
</template><Select>name<Form>vue
<template>
<Select name="role" default-value="member">
<SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
</template>在Inertia 中使用带属性的普通shadcn-vue //组件。
如需完整的 API,请参考技能(以及)。
<Form>nameInputLabelButton<Form>inertia-rails-formsreferences/vue.md核心模式: 用普通组件+手动错误显示替换shadcn-vue的//:
FormFieldFormItemFormMessagevue
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
</script>
<template>
<Form method="post" action="/users">
<template #default="{ errors, processing }">
<div class="space-y-4">
<div>
<Label for="name">Name</Label>
<Input id="name" name="name" />
<p v-if="errors.name" class="text-sm text-destructive">{{ errors.name }}</p>
</div>
<div>
<Label for="email">Email</Label>
<Input id="email" name="email" type="email" />
<p v-if="errors.email" class="text-sm text-destructive">{{ errors.email }}</p>
</div>
<Button type="submit" :disabled="processing">
{{ processing ? 'Creating...' : 'Create User' }}
</Button>
</div>
</template>
</Form>
</template><Select>name<Form>vue
<template>
<Select name="role" default-value="member">
<SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
</template>Dialog with Inertia Navigation
带Inertia导航的对话框
vue
<script setup lang="ts">
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { router } from '@inertiajs/vue3'
defineProps<{ open: boolean; user: User }>()
</script>
<template>
<Dialog
:open="open"
@update:open="(isOpen) => { if (!isOpen) router.replaceProp('show_dialog', false) }"
>
<DialogContent>
<DialogHeader>
<DialogTitle>{{ user.name }}</DialogTitle>
</DialogHeader>
<!-- content -->
</DialogContent>
</Dialog>
</template>vue
<script setup lang="ts">
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { router } from '@inertiajs/vue3'
defineProps<{ open: boolean; user: User }>()
</script>
<template>
<Dialog
:open="open"
@update:open="(isOpen) => { if (!isOpen) router.replaceProp('show_dialog', false) }"
>
<DialogContent>
<DialogHeader>
<DialogTitle>{{ user.name }}</DialogTitle>
</DialogHeader>
<!-- content -->
</DialogContent>
</Dialog>
</template>Table with Server-Side Sorting
服务端排序表格
vue
<script setup lang="ts">
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { router } from '@inertiajs/vue3'
defineProps<{ users: User[]; sort: string }>()
const handleSort = (column: string) => {
router.get('/users', { sort: column }, { preserveState: true })
}
</script>
<template>
<Table>
<TableHeader>
<TableRow>
<TableHead class="cursor-pointer" @click="handleSort('name')">
Name {{ sort === 'name' ? '↑' : '' }}
</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="user in users" :key="user.id">
<TableCell>{{ user.name }}</TableCell>
<TableCell>{{ user.email }}</TableCell>
</TableRow>
</TableBody>
</Table>
</template>Use (not ) for row links to preserve SPA navigation.
<Link><a>vue
<script setup lang="ts">
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { router } from '@inertiajs/vue3'
defineProps<{ users: User[]; sort: string }>()
const handleSort = (column: string) => {
router.get('/users', { sort: column }, { preserveState: true })
}
</script>
<template>
<Table>
<TableHeader>
<TableRow>
<TableHead class="cursor-pointer" @click="handleSort('name')">
Name {{ sort === 'name' ? '↑' : '' }}
</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="user in users" :key="user.id">
<TableCell>{{ user.name }}</TableCell>
<TableCell>{{ user.email }}</TableCell>
</TableRow>
</TableBody>
</Table>
</template>行链接请使用(而非)以保留SPA导航特性。
<Link><a>Toast with Flash Messages
带闪存消息的提示框
Flash config () is in . Flash access
() is in . This section covers toast UI wiring only.
flash_keysinertia-rails-controllersusePage().flashinertia-rails-pagesMANDATORY — READ ENTIRE FILE when implementing flash-based toasts with Sonner:
(~80 lines) — full
composable and Sonner toast provider. Do NOT load if only reading flash values without toast UI.
references/flash-toast.mduseFlash闪存配置()在中。闪存访问()在中。本节仅介绍提示框UI的关联配置。
flash_keysinertia-rails-controllersusePage().flashinertia-rails-pages强制要求 — 实现基于闪存的Sonner提示框时,请完整阅读以下文件:
(约80行)—— 包含完整的
组合式函数和Sonner提示框提供器。如果仅读取闪存值而不使用提示框UI,请不要加载该文件。
references/flash-toast.mduseFlashDark Mode (No Nuxt color-mode)
暗色模式(无需Nuxt color-mode)
npx shadcn-vue@latest init@custom-variant dark (&:is(.dark *));CRITICAL — prevent flash of wrong theme (FOUC): Add an inline script in
(before Vue hydrates):
<head>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>Use a composable (light/dark/system modes, localStorage persistence,
listener) instead of Nuxt color-mode. Toggle via class on
— no provider needed.
useAppearancematchMedia.dark<html>执行会为亮色/暗色模式生成CSS变量,
并在你的CSS中添加(适用于Tailwind v4)。变量本身无需额外配置。
npx shadcn-vue@latest init@custom-variant dark (&:is(.dark *));重要事项 — 避免主题闪烁(FOUC): 在中添加内联脚本(在Vue水合之前):
<head>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>使用组合式函数(支持亮色/暗色/系统模式、本地存储持久化、
监听器)替代Nuxt color-mode。通过标签的类切换主题 — 无需提供器。
useAppearancematchMedia<html>.darkVue-Specific Gotchas
Vue专属注意事项
v-model<Form><Form>namev-model<Form>vue
<!-- BAD — v-model value is ignored by <Form> on submit -->
<Form method="post" action="/users">
<Input v-model="name" />
</Form>
<!-- GOOD — name attribute is what <Form> reads -->
<Form method="post" action="/users">
<Input name="name" />
</Form>Use only with (where you explicitly manage ).
v-modeluseFormform.nameusePage()computed()vue
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
// BAD — not reactive, won't update when page changes:
// const user = page.props.auth.user
// GOOD — reactive, updates on navigation:
const user = computed(() => page.props.auth.user)
</script>Without , destructured values freeze at their initial state and
won't update after Inertia navigation.
computed()@update:open@closeupdate:openclose@closevue
<!-- BAD — @close is not emitted by shadcn-vue Dialog -->
<Dialog @close="handleClose">
<!-- GOOD — @update:open fires on open AND close -->
<Dialog :open="open" @update:open="(isOpen) => { if (!isOpen) handleClose() }">v-model<Form><Form>namev-model<Formvue
<!-- 错误用法 — <Form>提交时会忽略v-model的值 -->
<Form method="post" action="/users">
<Input v-model="name" />
</Form>
<!-- 正确用法 — <Form>读取的是name属性的值 -->
<Form method="post" action="/users">
<Input name="name" />
</Form>仅在使用(需显式管理)时使用。
useFormform.namev-modelusePage()computed()vue
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
// 错误用法 — 非响应式,页面变化时不会更新:
// const user = page.props.auth.user
// 正确用法 — 响应式,导航时会更新:
const user = computed(() => page.props.auth.user)
</script>如果不使用,解构后的值会冻结在初始状态,
Inertia导航后不会更新。
computed()Dialog的与 —— shadcn-vue Dialog触发
事件,而非事件。使用不会有任何效果:
@update:open@closeupdate:openclose@closevue
<!-- 错误用法 — shadcn-vue Dialog不会触发@close事件 -->
<Dialog @close="handleClose">
<!-- 正确用法 — @update:open会在打开和关闭时触发 -->
<Dialog :open="open" @update:open="(isOpen) => { if (!isOpen) handleClose() }">Troubleshooting
故障排查
| Symptom | Cause | Fix |
|---|---|---|
| Using shadcn-vue form components that depend on vee-validate | Replace with plain |
| Missing | Add |
| Dialog closes unexpectedly | Missing or wrong | Use |
| Flash of wrong theme (FOUC) | Missing inline | Add dark mode script before stylesheets |
| | Use |
| Shared props stale after navigation | Destructured | Wrap derived values in |
| 症状 | 原因 | 修复方案 |
|---|---|---|
| 使用了依赖vee-validate的shadcn-vue表单组件 | 替换为普通 |
| 缺少 | 为 |
| 对话框意外关闭 | | 使用 |
| 主题闪烁(FOUC) | | 在样式表之前添加暗色模式脚本 |
| | 使用name属性;仅在 |
| 共享props在导航后失效 | 未使用 | 将衍生值包裹在 |
Related Skills
相关技能
- Form component → +
inertia-rails-forms(references/vue.mdscoped slot, useForm)<Form> - Flash config → (flash_keys initializer)
inertia-rails-controllers - Flash access → +
inertia-rails-pages(usePage().flash)references/vue.md - URL-driven dialogs → +
inertia-rails-pages(router.get pattern)references/vue.md
- 表单组件 → +
inertia-rails-forms(references/vue.md作用域插槽、useForm)<Form> - 闪存配置 → (flash_keys初始化器)
inertia-rails-controllers - 闪存访问 → +
inertia-rails-pages(usePage().flash)references/vue.md - URL驱动的对话框 → +
inertia-rails-pages(router.get模式)references/vue.md
References
参考资料
Load (~200 lines) when building
shadcn-vue components beyond those shown above (Accordion, Sheet, Tabs, DropdownMenu,
AlertDialog with Inertia patterns).
references/components.mdDo NOT load for basic Form, Select, Dialog, or Table usage —
the examples above are sufficient.
components.md如果需要构建本文未展示的shadcn-vue组件(如折叠面板、侧边栏、标签页、下拉菜单、
带Inertia模式的确认对话框),请查看(约200行)。
references/components.md如果仅使用基础的表单、选择器、对话框或表格,请不要加载 ——
本文中的示例已足够使用。
components.md