shadcn-svelte Components by exceptionless/exceptionless
npx skills add https://github.com/exceptionless/exceptionless --skill 'shadcn-svelte Components'文档: shadcn-svelte.com | API 参考请使用
context7
使用 shadcn-svelte 组件(bits-ui)构建 UI。采用命名空间模式导入。
<script lang="ts">
import * as Dialog from '$comp/ui/dialog';
import * as DropdownMenu from '$comp/ui/dropdown-menu';
import * as Tooltip from '$comp/ui/tooltip';
import { Button } from '$comp/ui/button';
import { Input } from '$comp/ui/input';
</script>
当使用带有自定义元素(如 Button)的触发器组件时,务必使用 child 代码片段模式:
<!-- ✅ 正确:单一制表位,正确的无障碍访问性 -->
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon">
<Icon />
</Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>工具提示文本</Tooltip.Content>
</Tooltip.Root>
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<!-- ❌ 错误:创建两个可聚焦元素(双击制表位问题) -->
<Tooltip.Trigger>
<Button>内容</Button>
</Tooltip.Trigger>
<!-- ❌ 错误:手动样式复制按钮样式 -->
<Tooltip.Trigger class="hover:bg-accent inline-flex...">
<Icon />
</Tooltip.Trigger>
<!-- DropdownMenu -->
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline">
打开菜单
<ChevronDown />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<!-- Popover -->
<Popover.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="w-70">
选择日期
<CalendarIcon />
</Button>
{/snippet}
</Popover.Trigger>
<!-- Dialog -->
<Dialog.Trigger>
{#snippet child({ props })}
<Button {...props}>打开对话框</Button>
{/snippet}
</Dialog.Trigger>
<script lang="ts">
import * as Dialog from '$comp/ui/dialog';
import { Button } from '$comp/ui/button';
let openCreateDialog = $state(false);
</script>
<Button onclick={() => (openCreateDialog = true)}>创建</Button>
{#if openCreateDialog}
<Dialog.Root bind:open={openCreateDialog}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>创建组织</Dialog.Title>
<Dialog.Description>
向您的账户添加一个新组织。
</Dialog.Description>
</Dialog.Header>
<!-- 表单内容 -->
<Dialog.Footer>
<Button variant="outline" onclick={() => (openCreateDialog = false)}>
取消
</Button>
<Button type="submit">创建</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
{/if}
使用 open[组件名称]Dialog 模式
避免使用通用名称,如 showDialog 或 isOpen
<script lang="ts">
import * as DropdownMenu from '$comp/ui/dropdown-menu';
import { statusOptions } from './options';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline">
选择状态
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each statusOptions as option}
<DropdownMenu.Item onclick={() => handleSelect(option.value)}>
{option.label}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
// options.ts
import type { DropdownItem } from '$shared/types';
export enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending'
}
export const statusOptions: DropdownItem<Status>[] = [
{ value: Status.Active, label: '活跃' },
{ value: Status.Inactive, label: '非活跃' },
{ value: Status.Pending, label: '待处理' }
];
<Sheet.Root bind:open={openFiltersSheet}>
<Sheet.Content side="right">
<Sheet.Header>
<Sheet.Title>筛选器</Sheet.Title>
</Sheet.Header>
<!-- 筛选控件 -->
<Sheet.Footer>
<Button onclick={applyFilters}>应用</Button>
</Sheet.Footer>
</Sheet.Content>
</Sheet.Root>
使用 Svelte 数组语法处理条件类(而非 cn 工具函数):
<!-- ✅ 推荐:数组语法 -->
<Button class={['w-full', isActive && 'bg-primary']}>
点击我
</Button>
<div class={['flex items-center', expanded && 'bg-muted', className]}>
内容
</div>
<!-- ❌ 避免:cn 工具函数(旧模式) -->
<Button class={cn('w-full', isActive && 'bg-primary')}>
优先使用 href 导航而非 onclick/goto:
<!-- ✅ 推荐:原生导航 -->
<Button href="/organizations/new">创建</Button>
<!-- 仅在需要导航逻辑时使用 onclick -->
<Button onclick={async () => {
await saveData();
goto('/success');
}}>
保存并继续
</Button>
每周安装量
–
代码仓库
GitHub 星标数
2.5K
首次出现时间
–
安全审计
Documentation: shadcn-svelte.com | Use
context7for API reference
Use shadcn-svelte components (bits-ui) for UI. Import with namespace pattern.
<script lang="ts">
import * as Dialog from '$comp/ui/dialog';
import * as DropdownMenu from '$comp/ui/dropdown-menu';
import * as Tooltip from '$comp/ui/tooltip';
import { Button } from '$comp/ui/button';
import { Input } from '$comp/ui/input';
</script>
When using trigger components with custom elements like Button, always use thechild snippet pattern:
<!-- ✅ Correct: Single tab stop, proper accessibility -->
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon">
<Icon />
</Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>Tooltip text</Tooltip.Content>
</Tooltip.Root>
<!-- ❌ Wrong: Creates two focusable elements (double-tab issue) -->
<Tooltip.Trigger>
<Button>Content</Button>
</Tooltip.Trigger>
<!-- ❌ Wrong: Manual styling replicates button styles -->
<Tooltip.Trigger class="hover:bg-accent inline-flex...">
<Icon />
</Tooltip.Trigger>
<!-- DropdownMenu -->
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline">
Open Menu
<ChevronDown />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<!-- Popover -->
<Popover.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="w-70">
Select Date
<CalendarIcon />
</Button>
{/snippet}
</Popover.Trigger>
<!-- Dialog -->
<Dialog.Trigger>
{#snippet child({ props })}
<Button {...props}>Open Dialog</Button>
{/snippet}
</Dialog.Trigger>
<script lang="ts">
import * as Dialog from '$comp/ui/dialog';
import { Button } from '$comp/ui/button';
let openCreateDialog = $state(false);
</script>
<Button onclick={() => (openCreateDialog = true)}>Create</Button>
{#if openCreateDialog}
<Dialog.Root bind:open={openCreateDialog}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Create Organization</Dialog.Title>
<Dialog.Description>
Add a new organization to your account.
</Dialog.Description>
</Dialog.Header>
<!-- Form content -->
<Dialog.Footer>
<Button variant="outline" onclick={() => (openCreateDialog = false)}>
Cancel
</Button>
<Button type="submit">Create</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
{/if}
Use open[ComponentName]Dialog pattern
Avoid generic names like showDialog or isOpen
<script lang="ts">
import * as DropdownMenu from '$comp/ui/dropdown-menu';
import { statusOptions } from './options';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline">
Select Status
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each statusOptions as option}
<DropdownMenu.Item onclick={() => handleSelect(option.value)}>
{option.label}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
// options.ts
import type { DropdownItem } from '$shared/types';
export enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending'
}
export const statusOptions: DropdownItem<Status>[] = [
{ value: Status.Active, label: 'Active' },
{ value: Status.Inactive, label: 'Inactive' },
{ value: Status.Pending, label: 'Pending' }
];
<Sheet.Root bind:open={openFiltersSheet}>
<Sheet.Content side="right">
<Sheet.Header>
<Sheet.Title>Filters</Sheet.Title>
</Sheet.Header>
<!-- Filter controls -->
<Sheet.Footer>
<Button onclick={applyFilters}>Apply</Button>
</Sheet.Footer>
</Sheet.Content>
</Sheet.Root>
Use Svelte array syntax for conditional classes (NOT cn utility):
<!-- ✅ Preferred: Array syntax -->
<Button class={['w-full', isActive && 'bg-primary']}>
Click me
</Button>
<div class={['flex items-center', expanded && 'bg-muted', className]}>
Content
</div>
<!-- ❌ Avoid: cn utility (older pattern) -->
<Button class={cn('w-full', isActive && 'bg-primary')}>
Prefer href navigation over onclick/goto:
<!-- ✅ Preferred: Native navigation -->
<Button href="/organizations/new">Create</Button>
<!-- Use onclick only when navigation logic required -->
<Button onclick={async () => {
await saveData();
goto('/success');
}}>
Save and Continue
</Button>
Weekly Installs
–
Repository
GitHub Stars
2.5K
First Seen
–
Security Audits
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
109,600 周安装