Search

PythonReactSEOPDF
GitHub Skill

svelte-components

Svelte 5 component patterns for the Exceptionless SPA. Runes, reactivity, props, events, snippets, component organization, and shadcn-svelte integration. Keywords: Svelte 5, $state, $derived, $effect, $props, runes, onclick, snippets, {@render}, reactive, component composition, shadcn-svelte

exceptionless☆ 2,449 Repository stars513 Repository forks

Installation

Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.

View source ↗Guides

Skill instructions

Svelte Components

Documentation: svelte.dev | Use context7 for API reference

Visual Validation with Chrome MCP

Always verify UI changes visually using the Chrome MCP:

  1. After making component changes, use Chrome MCP to take a snapshot or screenshot
  2. Verify the component renders correctly and matches expected design
  3. Test interactive states (hover, focus, disabled) when applicable
  4. Check responsive behavior at different viewport sizes
  5. Default to the /next site path for verification

This visual validation loop catches styling issues, layout problems, and accessibility regressions that automated tests may miss.

File Organization

Naming Conventions
  • kebab-case for all component files: stack-status-badge.svelte, user-profile-card.svelte
  • Co-locate with feature slice, aligned with API controllers
Directory Structure
src/lib/features/
├── organizations/           # Matches OrganizationController
│   ├── components/
│   │   ├── organization-card.svelte
│   │   └── organization-switcher.svelte
│   ├── api.svelte.ts
│   ├── models.ts
│   └── schemas.ts
├── stacks/                  # Matches StackController
│   └── components/
│       └── stack-status-badge.svelte
└── shared/                  # Shared across features
    └── components/
        ├── data-table/
        ├── navigation/
        └── typography/

Always Use shadcn-svelte Components

Never use native HTML for buttons, inputs, or form elements:

<script lang="ts">
    import { Button } from '$comp/ui/button';
    import { Input } from '$comp/ui/input';
    import * as Card from '$comp/ui/card';
</script>

<!-- ✅ Use shadcn components -->
<Card.Root>
    <Card.Header>
        <Card.Title>Settings</Card.Title>
    </Card.Header>
    <Card.Content>
        <Input placeholder="Enter value" />
    </Card.Content>
    <Card.Footer>
        <Button>Save</Button>
    </Card.Footer>
</Card.Root>

<!-- ❌ Never use native HTML -->
<button class="...">Save</button>
<input type="text" />

Runes

$state - Reactive State
<script lang="ts">
    let count = $state(0);
    let user = $state<User | null>(null);
    let items = $state<string[]>([]);
</script>
$derived - Computed Values
<script lang="ts">
    let count = $state(0);
    let doubled = $derived(count * 2);
    let isEven = $derived(count % 2 === 0);

    // Complex derived
    let summary = $derived.by(() => {
        return items.filter(i => i.active).map(i => i.name).join(', ');
    });
</script>
$effect - Side Effects
<script lang="ts">
    let searchTerm = $state('');

    $effect(() => {
        console.log('Search term changed:', searchTerm);
        return () => console.log('Cleaning up');
    });
</script>

Props

<script lang="ts">
    interface Props {
        name: string;
        count?: number;
        onUpdate?: (value: number) => void;
        children?: import('svelte').Snippet;
    }

    let { name, count = 0, onUpdate, children }: Props = $props();
</script>

Event Handling

Use onclick instead of on:click:

<Button onclick={() => handleClick()}>Click me</Button>
<Input oninput={(e) => (value = e.currentTarget.value)} />

Snippets (Content Projection)

Replace <slot> with snippets. From login/+page.svelte:

<form.Subscribe selector={(state) => state.errors}>
    {#snippet children(errors)}
        <ErrorMessage message={getFormErrorMessages(errors)}></ErrorMessage>
    {/snippet}
</form.Subscribe>

<form.Field name="email">
    {#snippet children(field)}
        <Field.Field data-invalid={ariaInvalid(field)}>
            <Field.Label for={field.name}>Email</Field.Label>
            <Input
                id={field.name}
                value={field.state.value}
                oninput={(e) => field.handleChange(e.currentTarget.value)}
            />
            <Field.Error errors={mapFieldErrors(field.state.meta.errors)} />
        </Field.Field>
    {/snippet}
</form.Field>

Class Merging

Use array syntax for conditional classes:

<div class={['flex items-center', expanded && 'bg-muted', className]}>
    Content
</div>

<Button class={['w-full', isActive && 'bg-primary']}>Save</Button>

Keyboard Accessibility

All interactive components must be keyboard accessible:

  • Use Button component (provides focus handling automatically)
  • Ensure custom interactions have tabindex and keyboard handlers
  • Test with keyboard-only navigation

See accessibility for WCAG guidelines.

Imports

<script lang="ts">
    // Use $app/state instead of $app/stores
    import { page } from '$app/state';

    // Access page data
    let currentPath = $derived(page.url.pathname);
</script>

References

Examples

Read the usage examples in the source repository.

View source ↗

Source history

Indexed on
2026-01-20
Index updated
2026-01-26
Branch
main

Index and sync dates describe this directory. View the source repository for code history.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!