mcpField
S
shadcn-svelte Components

shadcn-svelte Components

github.com/exceptionlessデザイン・UI2.5k120 回インストール今日更新
リポジトリ

shadcn-svelte Components について

shadcn-svelte と bits-ui で画面を構築する開発者向けのコーディングスキルです。トリガー、ダイアログ、メニュー、スタイル、ナビゲーションの実装パターンを示します。

主な機能

  1. 01コンポーネントのインポート形式
  2. 02子スニペットによるトリガー形式
  3. 03ダイアログの命名規則
  4. 04ドロップダウン項目の整理
  5. 05クラスとナビゲーションの指針

活用シーン

  1. 01アクセシブルなメニュートリガーの作成
  2. 02一貫したダイアログ状態の命名
  3. 03ドロップダウン項目の整理
  4. 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>

コメント