About svelte-components
A Svelte component skill for developers, documenting conventions for component structure, Svelte runes, props, and interactions. It also calls for visual and keyboard-accessibility checks during UI work.
Key features
- 01Component naming and organization
- 02Guidance on Svelte runes and props
- 03shadcn-svelte component conventions
- 04Event and snippet patterns
- 05Visual and keyboard accessibility checks
Use cases
- 01Building consistent Svelte components
- 02Reviewing component structure and state
- 03Validating UI appearance and interactions
MCP support
The skill directs UI developers to use Chrome MCP to capture snapshots or screenshots and verify rendering, interaction states, and responsive layouts.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ exceptionless/ exceptionless --skill svelte-componentsAfter installing, type/svelte-componentsin Claude Code to confirm it loaded.
Details
From the project READMESvelte Components
Documentation: svelte.dev | Use
context7for API reference
Visual Validation with Chrome MCP
Always verify UI changes visually using the Chrome MCP:
- After making component changes, use Chrome MCP to take a snapshot or screenshot
- Verify the component renders correctly and matches expected design
- Test interactive states (hover, focus, disabled) when applicable
- Check responsive behavior at different viewport sizes
- Default to the
/nextsite 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 src/Exceptionless.Web/ClientApp/src/routes/(auth)/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
Buttoncomponent (provides focus handling automatically) - Ensure custom interactions have
tabindexand 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
- shadcn-svelte — UI component patterns and trigger snippets
- accessibility — WCAG guidelines and keyboard navigation