mcpField
S
shadcn-svelte-inertia

shadcn-svelte-inertia

github.com/inertia-rails开发7094 次安装37 周前更新
代码仓库

关于 shadcn-svelte-inertia

此技能将 shadcn-svelte 和 bits-ui 模式适配到使用 Inertia.js 与 Svelte 的 Rails 应用,而非 SvelteKit。内容涵盖表单、导航、对话框、主题设置及常见集成问题。

主要功能

  1. 01将 shadcn-svelte 模式适配到 Inertia 与 Rails
  2. 02说明 Inertia 导航和表单约定
  3. 03涵盖对话框、表格与 Toast 接入
  4. 04介绍深色模式和响应式页面状态
  5. 05提供故障排查及组件参考

适用场景

  1. 01在 Rails 和 Inertia 应用中构建 shadcn-svelte 界面
  2. 02将 SvelteKit 示例改写为 Inertia 约定
  3. 03排查表单提交和页面属性未更新问题
  4. 04根据 Flash 消息添加 Toast 通知

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/inertia-rails/skills --skill shadcn-svelte-inertia

安装后在 Claude Code 中输入/shadcn-svelte-inertia确认已加载。

附带文件references/components.mdreferences/flash-toast.md

详细介绍

适用于 Inertia Rails 的 shadcn-svelte

为 Inertia.js + Rails + Svelte 改编的 shadcn-svelte(bits-ui)模式。不是 SvelteKit。

使用 shadcn-svelte 示例前,请先确认:

  • 是否使用了 SvelteKit 专属 API?(goto、$app/navigation、load 函数、+page.svelte)→ 替换为 Inertia router、服务端 props、页面组件
  • 是否使用了 sveltekit-superforms + zod? → 替换为 Inertia <Form> + name 属性。Inertia 会处理 CSRF、错误、重定向和处理中状态。

与 SvelteKit 默认设置的主要区别

shadcn-svelte 默认设置(SvelteKit)Inertia 对应项
来自 $app/navigation 的 goto()来自 @inertiajs/svelte 的 router
load 函数通过 Rails 控制器提供服务端渲染的 props
+page.svelte / +layout.svelte使用模块脚本布局的默认导出
sveltekit-superforms + zodInertia <Form> 组件
<svelte:head>(由 SvelteKit 自动管理)<svelte:head>(相同——Svelte 中没有 Inertia <Head>)

设置

运行 npx shadcn-svelte@latest init。如果 tsconfig.json 中没有 @/ 解析别名,请添加。 不要在 vite.config.ts 中添加 @/ 解析别名——vite-plugin-ruby 已经提供了这些别名。

Inertia <Form> 中的 shadcn-svelte 输入组件

在 Inertia <Form> 内使用带有 name 属性的普通 shadcn-svelte Input/Label/Button。 完整的 <Form> API 请参阅 inertia-rails-forms 技能(以及 references/svelte.md)。

关键模式: 使用 {#snippet} 访问表单状态:

<script lang="ts">
  import { Form } from '@inertiajs/svelte'
  import { Input } from '$lib/components/ui/input'
  import { Label } from '$lib/components/ui/label'
  import { Button } from '$lib/components/ui/button'
</script>

<Form method="post" action="/users">
  {#snippet children({ errors, processing })}
    <div class="space-y-4">
      <div>
        <Label for="name">Name</Label>
        <Input id="name" name="name" />
        {#if errors.name}<p class="text-sm text-destructive">{errors.name}</p>{/if}
      </div>

      <div>
        <Label for="email">Email</Label>
        <Input id="email" name="email" type="email" />
        {#if errors.email}<p class="text-sm text-destructive">{errors.email}</p>{/if}
      </div>

      <Button type="submit" disabled={processing}>
        {processing ? 'Creating...' : 'Create User'}
      </Button>
    </div>
  {/snippet}
</Form>

Svelte 4:使用 <Form let:errors let:processing>,而不是 {#snippet}。

<Select> 需要 name prop 才能与 Inertia <Form> 集成:

<Select name="role" value="member">
  <SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
  <SelectContent>
    <SelectItem value="admin">Admin</SelectItem>
    <SelectItem value="member">Member</SelectItem>
  </SelectContent>
</Select>

使用 Inertia 导航的对话框

<script lang="ts">
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from '$lib/components/ui/dialog'
  import { router } from '@inertiajs/svelte'

  let { open, user }: { open: boolean; user: User } = $props()
</script>

<Dialog
  {open}
  onOpenChange={(isOpen) => { if (!isOpen) router.replaceProp('show_dialog', false) }}
>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>{user.name}</DialogTitle>
    </DialogHeader>
    
  </DialogContent>
</Dialog>

Svelte 4:使用 on:openChange,而不是 onOpenChange。

服务端排序表格

<script lang="ts">
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'
  import { router } from '@inertiajs/svelte'

  let { users, sort }: { users: User[]; sort: string } = $props()

  const handleSort = (column: string) => {
    router.get('/users', { sort: column }, { preserveState: true })
  }
</script>

<Table>
  <TableHeader>
    <TableRow>
      <TableHead class="cursor-pointer" onclick={() => handleSort('name')}>
        Name {sort === 'name' ? '↑' : ''}
      </TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {#each users as user (user.id)}
      <TableRow>
        <TableCell>{user.name}</TableCell>
        <TableCell>{user.email}</TableCell>
      </TableRow>
    {/each}
  </TableBody>
</Table>

行链接请使用 <Link> 或 use:inertia(不要使用 <a>),以保留 SPA 导航。

使用 Flash 消息显示 Toast

Flash 配置(flash_keys)位于 inertia-rails-controllers 中。Flash 访问($page.flash)位于 inertia-rails-pages 中。本节仅介绍 Toast UI 的接线方式。

必须——实现基于 flash 的 Sonner Toast 时,请阅读整个文件: references/flash-toast.md(约 80 行)——完整的 flash 侦听器和 svelte-sonner 集成。如果只读取 flash 值而不使用 Toast UI,请勿加载此文件。

深色模式

npx shadcn-svelte@latest init 会为浅色/深色模式生成 CSS 变量,并在 CSS 中添加 @custom-variant dark (&:is(.dark *));(Tailwind v4)。

重要——避免错误主题闪烁(FOUC): 在 <head> 中添加内联脚本(位于 Svelte hydration 之前):

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

使用 useAppearance 模式(浅色/深色/系统模式、localStorage 持久化、matchMedia 侦听器)。通过 <html> 上的 .dark 类切换主题。

使用 <svelte:head>,而不是 <Head>

Svelte 使用原生的 <svelte:head>——Svelte 中没有 Inertia <Head> 组件。 这同样适用于 shadcn 模式(例如,在对话框视图中设置页面标题):

<svelte:head>
  <title>{user.name} - Profile</title>
</svelte:head>

Svelte 特有注意事项

bind:value 与 Inertia <Form> 不兼容——<Form> 会在提交时从输入元素的 name 属性读取值,而不是从 Svelte 的响应式绑定中读取。 使用 bind:value 会产生第二个数据源,而 <Form> 会忽略它:


<Form method="post" action="/users">
  <Input bind:value={name} />
</Form>


<Form method="post" action="/users">
  <Input name="name" />
</Form>

仅在 useForm 中使用 bind:value(此时你需要显式管理 $form.name)。

$page store 更新具有响应性,但解构出的值没有响应性:

<script lang="ts">
  import { page } from '@inertiajs/svelte'

  // BAD — snapshot, won't update after navigation:
  // let user = $page.props.auth.user

  // GOOD — use $derived for reactive access:
  let user = $derived($page.props.auth.user)
</script>

Svelte 4:使用 $: user = $page.props.auth.user(响应式语句)。

使用 use:inertia 指令作为 <Link> 的替代方案——对于无法使用 <Link> 的元素(例如表格行、自定义组件),请使用此 action:

<script lang="ts">
  import { inertia } from '@inertiajs/svelte'
</script>

<tr use:inertia={{ href: `/users/${user.id}` }} class="cursor-pointer">
  <td>{user.name}</td>
</tr>

bits-ui 过渡 props 与 Inertia 导航——带有 transition* props 的 bits-ui 组件,在 Inertia 页面转换期间,如果退出动画耗时长于导航,可能会显示过时内容。请设置较短的持续时间,或在内容依赖页面 props 时使用 forceMount。

故障排除

症状原因修复方法
表单组件崩溃使用了依赖 superforms 的 shadcn-svelte 表单组件替换为普通的 Input/Label + errors.field 显示
未提交 Select 的值缺少 name 属性为 <Select> 添加 name="field"
对话框意外关闭缺少或错误的 onOpenChange 处理程序使用 onOpenChange={(open) => { if (!open) closeHandler() }}
错误主题闪现(FOUC)<head> 中缺少内联 <script>在样式表之前添加深色模式脚本
未提交 bind:value<Form> 读取的是 name 属性,而不是 Svelte 绑定使用 name 属性;仅在 useForm 中使用 bind:value
导航后共享属性过时解构 $page 时未使用 $derived使用 $derived($page.props.auth.user) 实现响应式访问

相关技能

  • 表单组件 → inertia-rails-forms + references/svelte.md(<Form> 代码片段、useForm)
  • Flash 配置 → inertia-rails-controllers(flash_keys 初始化器)
  • Flash 访问 → inertia-rails-pages + references/svelte.md($page.flash)
  • 由 URL 驱动的对话框 → inertia-rails-pages + references/svelte.md(router.get 模式)
  • use:inertia 指令 → inertia-rails-pages + references/svelte.md

参考资料

构建上述示例之外的 shadcn-svelte 组件(Accordion、Sheet、Tabs、DropdownMenu、 带 Inertia 模式的 AlertDialog)时,加载 references/components.md(约 200 行)。

基本的 Form、Select、Dialog 或 Table 用法无需加载 components.md — 上述示例已足够。

评论

支持 Markdown