shadcn-svelte components

shadcn-svelte Components

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "shadcn-svelte components" with this command: npx skills add exceptionless/exceptionless/exceptionless-exceptionless-shadcn-svelte-components

shadcn-svelte Components

Documentation: shadcn-svelte.com | Use context7 for API reference

Use shadcn-svelte components (bits-ui) for UI. Import with namespace pattern.

Import 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>

Trigger Components - Child Snippet Pattern

When using trigger components with custom elements like Button, always use the child 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>

Why This Pattern?

  • Single Tab Stop: Creates only one focusable element

  • Proper Props Delegation: ARIA attributes pass through correctly

  • Accessibility: Maintains keyboard navigation

  • Official Pattern: Documented shadcn-svelte/bits-ui pattern

Wrong Patterns

<!-- ❌ 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>

Apply to All Triggers

<!-- 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>

Dialog Pattern

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

        &#x3C;!-- Form content -->

        &#x3C;Dialog.Footer>
            &#x3C;Button variant="outline" onclick={() => (openCreateDialog = false)}>
                Cancel
            &#x3C;/Button>
            &#x3C;Button type="submit">Create&#x3C;/Button>
        &#x3C;/Dialog.Footer>
    &#x3C;/Dialog.Content>
&#x3C;/Dialog.Root>

{/if}

Dialog Naming Convention

  • Use open[ComponentName]Dialog pattern

  • Avoid generic names like showDialog or isOpen

<script lang="ts"> let openSuspendOrganizationDialog = $state(false); let openMarkStackDiscardedDialog = $state(false); let openInviteUserDialog = $state(false); </script>

DropdownMenu with Options

<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 File Pattern

// 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 (Slide-out Panel)

<Sheet.Root bind:open={openFiltersSheet}> <Sheet.Content side="right"> <Sheet.Header> <Sheet.Title>Filters</Sheet.Title> </Sheet.Header>

    &#x3C;!-- Filter controls -->

    &#x3C;Sheet.Footer>
        &#x3C;Button onclick={applyFilters}>Apply&#x3C;/Button>
    &#x3C;/Sheet.Footer>
&#x3C;/Sheet.Content>

</Sheet.Root>

Class Merging with Array Syntax

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')}>

Navigation Preference

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>

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

tanstack-form

No summary provided by upstream source.

Repository SourceNeeds Review
General

.net conventions

No summary provided by upstream source.

Repository SourceNeeds Review
General

stripe-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review