
Svelte Components
- 24 installs
- 217 repo stars
- Updated August 3, 2026
- spences10/svelte-claude-skills
Helps with ai & agent building tasks.
About
svelte-components is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- svelte-components
- AI & Agent Building
- AI-coding skill
Svelte Components by the numbers
- 24 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #9,912 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/spences10/svelte-claude-skills --skill svelte-componentsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 217 |
| Last updated | August 3, 2026 |
| Repository | spences10/svelte-claude-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Svelte Components
Quick Start
Component libraries: Bits UI (headless) | Ark UI | Melt UI (primitives)
Form trick: Use form attribute when form can't wrap inputs:
<form id="my-form" action="/submit"><!-- outside table --></form>
<table>
<tr>
<td><input form="my-form" name="email" /></td>
<td><button form="my-form">Submit</button></td>
</tr>
</table>Web Components
// svelte.config.js
export default {
compilerOptions: {
customElement: true,
},
};<!-- MyButton.svelte -->
<svelte:options customElement="my-button" />
<button><slot /></button>Reference Files
- component-libraries.md - Bits
UI, Ark UI setup
- web-components.md - Building custom
elements
- form-patterns.md - Advanced form
handling
Notes
- Bits UI 1.0: flexible, unstyled, accessible components for Svelte
- Form
defaultValueattribute enables easy form resets - Use snippets to wrap rich HTML in custom select options
- Last verified: 2025-01-14
<!-- PROGRESSIVE DISCLOSURE GUIDELINES:
- Keep this file ~50 lines total (max ~150 lines)
- Use 1-2 code blocks only (recommend 1)
- Keep description <200 chars for Level 1 efficiency
- Move detailed docs to references/ for Level 3 loading
- This is Level 2 - quick reference ONLY, not a manual
-->
Component Libraries
Bits UI
Headless, unstyled, accessible components for Svelte.
pnpm add bits-ui<script>
import { Button } from 'bits-ui';
</script>
<Button.Root class="my-button">Click me</Button.Root>Key features:
- Fully unstyled - bring your own CSS
- Accessible by default (ARIA, keyboard nav)
- Composable compound components
Docs: bits-ui.com
---
Ark UI
Full-featured component library with Svelte support.
pnpm add @ark-ui/svelte<script>
import { Dialog } from '@ark-ui/svelte';
</script>
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>Title</Dialog.Title>
<Dialog.Description>Description</Dialog.Description>
<Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>Docs: ark-ui.com
---
Melt UI
Low-level primitives (builders) for maximum flexibility.
pnpm add @melt-ui/svelte<script>
import { createDialog } from '@melt-ui/svelte';
const {
elements: { trigger, portalled, overlay, content, title, close },
states: { open },
} = createDialog();
</script>
<button use:melt={$trigger}>Open</button>
{#if $open}
<div use:melt={$portalled}>
<div use:melt={$overlay} />
<div use:melt={$content}>
<h2 use:melt={$title}>Title</h2>
<button use:melt={$close}>Close</button>
</div>
</div>
{/if}Key difference: Melt uses builders (functions) instead of components.
Docs: melt-ui.com
---
Which to Choose?
| Library | Style | Approach | Best For |
|---|---|---|---|
| Bits UI | Unstyled | Components | Quick accessible UI |
| Ark UI | Unstyled | Components | Feature-rich apps |
| Melt UI | Unstyled | Builders | Maximum control |
All three work with Svelte 5 runes.
Form Patterns
Form Attribute Trick
When you can't nest a form (e.g., inside tables), use the form attribute:
<form id="add-item" action="?/add" method="POST"></form>
<table>
<tbody>
{#each items as item}
<tr>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
{/each}
<tr>
<td><input form="add-item" name="name" required /></td>
<td
><input
form="add-item"
name="price"
type="number"
required
/></td
>
<td><button form="add-item">Add</button></td>
</tr>
</tbody>
</table>Benefits:
- Form can be anywhere in the document
- Submit with Enter works
- FormData collection works
- Accessible by default
Default Values and Reset
Forms support defaultValue for easy resets:
<script>
let name = $state('');
</script>
<form onreset={() => (name = '')}>
<input bind:value={name} defaultValue="" />
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>Progressive Enhancement
<script>
import { enhance } from '$app/forms';
let submitting = $state(false);
</script>
<form
method="POST"
use:enhance={() => {
submitting = true;
return async ({ update }) => {
await update();
submitting = false;
};
}}
>
<input name="email" type="email" required />
<button disabled={submitting}>
{submitting ? 'Saving...' : 'Save'}
</button>
</form>Form Validation with Valibot
// +page.server.ts
import * as v from 'valibot';
import { fail } from '@sveltejs/kit';
const ContactSchema = v.object({
email: v.pipe(v.string(), v.email()),
message: v.pipe(v.string(), v.minLength(10)),
});
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const data = Object.fromEntries(formData);
const result = v.safeParse(ContactSchema, data);
if (!result.success) {
return fail(400, {
data,
errors: v.flatten(result.issues),
});
}
// Process valid data
await saveContact(result.output);
},
};<!-- +page.svelte -->
<script>
let { form } = $props();
</script>
<form method="POST">
<label>
Email
<input
name="email"
type="email"
value={form?.data?.email ?? ''}
/>
{#if form?.errors?.nested?.email}
<span class="error">{form.errors.nested.email[0]}</span>
{/if}
</label>
<label>
Message
<textarea name="message">{form?.data?.message ?? ''}</textarea>
{#if form?.errors?.nested?.message}
<span class="error">{form.errors.nested.message[0]}</span>
{/if}
</label>
<button>Send</button>
</form>Multiple Forms on One Page
<form action="?/subscribe" method="POST">
<input name="email" type="email" />
<button>Subscribe</button>
</form>
<form action="?/contact" method="POST">
<input name="message" />
<button>Send</button>
</form>// +page.server.ts
export const actions = {
subscribe: async ({ request }) => {
// Handle subscription
},
contact: async ({ request }) => {
// Handle contact
},
};Web Components with Svelte
Basic Setup
// svelte.config.js
export default {
compilerOptions: {
customElement: true, // Enable for entire project
},
};Or per-component:
<svelte:options customElement="my-element" />
<script>
let { name = 'World' } = $props();
</script>
<p>Hello {name}!</p>Gotchas
1. Self-Closing Tags
Svelte 5 requires closing tags. This affects custom elements:
<!-- WRONG -->
<my-element />
<!-- RIGHT -->
<my-element></my-element>2. Nested HTML in Options
<option> with nested HTML causes compiler errors:
<!-- WRONG - compiler error -->
<select>
<option><div>Rich content</div></option>
</select>
<!-- WORKAROUND - use snippets -->
{#snippet optionContent()}
<div>Rich content</div>
{/snippet}
<select>
<option>{@render optionContent()}</option>
</select>3. Shadow DOM Styling
Styles are scoped to shadow DOM by default:
<svelte:options customElement="styled-button" />
<button>
<slot />
</button>
<style>
/* Only affects this component's shadow DOM */
button {
background: blue;
}
</style>Exposing Props as Attributes
<svelte:options
customElement={{
tag: 'my-counter',
props: {
count: { reflect: true, type: 'Number' },
},
}}
/>
<script>
let { count = 0 } = $props();
</script>
<button onclick={() => count++}>{count}</button>Events
Dispatch custom events:
<svelte:options customElement="event-button" />
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button onclick={() => dispatch('clicked', { time: Date.now() })}>
Click me
</button><!-- Usage -->
<event-button></event-button>
<script>
document
.querySelector('event-button')
.addEventListener('clicked', (e) => console.log(e.detail));
</script>Library Distribution
For library authors:
// package.json
{
"svelte": "./dist/index.js",
"exports": {
".": {
"svelte": "./dist/index.js"
}
},
"keywords": ["svelte"],
"peerDependencies": {
"svelte": "^5.0.0"
}
}Important: Always include svelte in keywords and peerDependencies.