shadcn-svelte Components について
shadcn-svelte と bits-ui で画面を構築する開発者向けのコーディングスキルです。トリガー、ダイアログ、メニュー、スタイル、ナビゲーションの実装パターンを示します。
主な機能
- 01コンポーネントのインポート形式
- 02子スニペットによるトリガー形式
- 03ダイアログの命名規則
- 04ドロップダウン項目の整理
- 05クラスとナビゲーションの指針
活用シーン
- 01アクセシブルなメニュートリガーの作成
- 02一貫したダイアログ状態の命名
- 03ドロップダウン項目の整理
- 04画面へのスライドパネル追加
MCP 対応
README は API リファレンスに context7 を使うよう記載していますが、このスキルと MCP の連携方法は説明していません。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ exceptionless/ exceptionless --skill shadcn-svelte-componentsインストール後、Claude Code で/shadcn-svelte-componentsを入力して読み込まれたか確認します。
詳細
shadcn-svelte コンポーネント
ドキュメント: shadcn-svelte.com | API リファレンスには
context7を使用してください
UI には shadcn-svelte コンポーネント(bits-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 text</Tooltip.Content>
</Tooltip.Root>
このパターンを使う理由
- タブストップが 1 つ:フォーカス可能な要素を 1 つだけ作成します
- 適切なプロパティの委譲:ARIA 属性が正しく引き継がれます
- アクセシビリティ:キーボードナビゲーションを維持します
- 公式パターン:shadcn-svelte/bits-ui でドキュメント化されているパターンです
誤ったパターン
<Tooltip.Trigger>
<Button>Content</Button>
</Tooltip.Trigger>
<Tooltip.Trigger class="hover:bg-accent inline-flex...">
<Icon />
</Tooltip.Trigger>
すべてのトリガーに適用
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline">
Open Menu
<ChevronDown />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<Popover.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" class="w-70">
Select Date
<CalendarIcon />
</Button>
{/snippet}
</Popover.Trigger>
<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>
<Dialog.Footer>
<Button variant="outline" onclick={() => (openCreateDialog = false)}>
Cancel
</Button>
<Button type="submit">Create</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
{/if}
ダイアログの命名規則
open[ComponentName]Dialogパターンを使用しますshowDialogやisOpenなどの汎用的な名前は避けてください
<script lang="ts">
let openSuspendOrganizationDialog = $state(false);
let openMarkStackDiscardedDialog = $state(false);
let openInviteUserDialog = $state(false);
</script>
オプション付きの DropdownMenu
<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(スライドアウトパネル)
<Sheet.Root bind:open={openFiltersSheet}>
<Sheet.Content side="right">
<Sheet.Header>
<Sheet.Title>Filters</Sheet.Title>
</Sheet.Header>
<Sheet.Footer>
<Button onclick={applyFilters}>Apply</Button>
</Sheet.Footer>
</Sheet.Content>
</Sheet.Root>
配列構文によるクラスのマージ
条件付きクラスには Svelte の配列構文を使用してください(cn ユーティリティは使用しないでください)。
<Button class={['w-full', isActive && 'bg-primary']}>
Click me
</Button>
<div class={['flex items-center', expanded && 'bg-muted', className]}>
Content
</div>
<Button class={cn('w-full', isActive && 'bg-primary')}>
ナビゲーションの推奨事項
onclick/goto よりも href によるナビゲーションを優先してください。
<Button href="/organizations/new">Create</Button>
<Button onclick={async () => {
await saveData();
goto('/success');
}}>
Save and Continue
</Button>
コメント
Markdown 対応