
Shadcn Svelte
- 2.8k installs
- 9k repo stars
- Updated August 2, 2026
- huntabyte/shadcn-svelte
shadcn-svelte is an agent skill that manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing ui. provides project context, component docs, and usage examples. applies w
About
shadcn-svelte is an agent skill from huntabyte/shadcn-svelte that manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing ui. provides project context, component docs, and usage examples. applies when working with . # shadcn-svelte A framework for building UI, components, and design systems for Svelte. Components are added as source to the user's project via the CLI. > **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn-svelte@latest`, `pnpm dlx shadcn-svelte@latest`, or `bunx --bun shadcn-svelte@latest` — based on the project Developers invoke shadcn-svelte during build/frontend work for frontend development tasks. The skill documents triggers, prerequisites, and step-by-step workflows grounded in SKILL.md. Compatible with Claude Code, Cursor, and Codex agent runtimes that load marketplace skills.
- A framework for building UI, components, and design systems for Svelte. Components are added as source to the user's pro
- Each component lives in its own folder with an `index.ts` barrel. Match the [installation docs](https://shadcn-svelte.co
- import * as Dialog from "$lib/components/ui/dialog";
- import { Button } from "$lib/components/ui/button";
- import { Separator } from "$lib/components/ui/separator";
Shadcn Svelte by the numbers
- 2,847 all-time installs (skills.sh)
- +306 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #187 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
shadcn-svelte capabilities & compatibility
- Capabilities
- a framework for building ui, components, and des · each component lives in its own folder with an ` · import * as dialog from "$lib/components/ui/dial · import { button } from "$lib/components/ui/butto · import { separator } from "$lib/components/ui/se
- Use cases
- orchestration
What shadcn-svelte says it does
A framework for building UI, components, and design systems for Svelte. Components are added as source to the user's project via the CLI.
Read `components.json` at the project root and, when you need the live file layout, list the directory given by the `aliases.ui` path (resolved with the same rules as the CLI).
Each component lives in its own folder with an `index.ts` barrel. Match the [installation docs](https://shadcn-svelte.com/docs/installation):
npx skills add https://github.com/huntabyte/shadcn-svelte --skill shadcn-svelteAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.8k |
|---|---|
| repo stars | ★ 9k |
| Last updated | August 2, 2026 |
| Repository | huntabyte/shadcn-svelte ↗ |
What it does
Manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with
Who is it for?
Developers working on frontend development during build tasks.
Skip if: Tasks outside Frontend Development scope described in SKILL.md.
When should I use this skill?
Manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with
What you get
Completed frontend development workflow aligned with SKILL.md steps.
- installed ui component folders
- corrected svelte imports
- updated registry component files
By the numbers
- Allowed-tools front matter permits npx, pnpm dlx, and bunx shadcn-svelte@latest CLI commands
Files
shadcn-svelte
A framework for building UI, components, and design systems for Svelte. Components are added as source to the user's project via the CLI.
IMPORTANT: Run all CLI commands using the project's package runner:npx shadcn-svelte@latest,pnpm dlx shadcn-svelte@latest, orbunx --bun shadcn-svelte@latest— based on the project's package manager. Examples below usenpx shadcn-svelte@latestbut substitute the correct runner for the project.
Current Project Context
Read components.json at the project root and, when you need the live file layout, list the directory given by the aliases.ui path (resolved with the same rules as the CLI).
Imports (Svelte)
Each component lives in its own folder with an index.ts barrel. Match the installation docs:
- Multi-part components (dialog, select, card, field, tabs, …):
import * as Dialog from "$lib/components/ui/dialog"thenDialog.Content,Dialog.Title,Card.Root,Card.Header, etc. — whatever the barrel exports (short names and/orRoot as …aliases). - Single-component barrels (only one meaningful component in the folder): named imports —
import { Button } from "$lib/components/ui/button"and<Button>, notimport * as Button+Button.Root. Same pattern for{ Input },{ Badge },{ Spinner },{ Checkbox },{ Separator },{ Skeleton }, etc.
import * as Dialog from "$lib/components/ui/dialog";
import { Button } from "$lib/components/ui/button";
import { Separator } from "$lib/components/ui/separator";Use the real aliases from components.json (often $lib/components/ui/...), not hardcoded paths.
Principles
1. Use existing components first. Run npx shadcn-svelte@latest add with no arguments to browse available components, or check Components before writing custom UI. 2. Compose, don't reinvent. Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table. 3. Use built-in variants before custom styles. variant="outline", size="sm", etc. 4. Use semantic colors. bg-primary, text-muted-foreground — never raw values like bg-blue-500.
Critical Rules
These rules are always enforced. Each links to a file with Incorrect/Correct code pairs.
Styling & Tailwind → styling.md
- `class` for layout, not styling. Never override component colors or typography.
- *No `space-x-
orspace-y-`. Use `flex` with `gap-. For vertical stacks,flex flex-col gap-*`. - *Use `size-
when width and height are equal.**size-10notw-10 h-10`. - Use `truncate` shorthand. Not
overflow-hidden text-ellipsis whitespace-nowrap. - No manual `dark:` color overrides. Use semantic tokens (
bg-background,text-muted-foreground). - Use `cn()` for conditional classes. Don't write manual template literal ternaries.
- No manual `z-index` on overlay components. Dialog, Sheet, Popover, etc. handle their own stacking.
Forms & Inputs → forms.md
- Forms use `Field.FieldGroup` + `Field.Field`. Never use raw
divwithspace-y-*orgrid gap-*for form layout. - `InputGroup` uses `InputGroup.Input`/`InputGroup.Textarea`. Never raw
Input/TextareainsideInputGroup.Root. - Buttons inside inputs use `InputGroup.Root` + `InputGroup.Addon`.
- Option sets (2–7 choices) use `ToggleGroup`. Don't loop
Buttonwith manual active state. - `Field.FieldSet` + `Field.FieldLegend` for grouping related checkboxes/radios. Don't use a
divwith a heading. - Field validation uses `data-invalid` + `aria-invalid`.
data-invalidonField,aria-invalidon the control. For disabled:data-disabledonField,disabledon the control.
Component Structure → composition.md
- Items always inside their Group.
Select.Item→Select.Group.DropdownMenu.Item→DropdownMenu.Group.Command.Item→Command.Group. - Custom triggers. Wrap controls in
Dialog.Trigger/AlertDialog.Trigger, or control open state withbind:openon the root — see component docs. - Dialog, Sheet, and Drawer always need a Title.
Dialog.Title,Sheet.Title,Drawer.Titlerequired for accessibility. Useclass="sr-only"if visually hidden. - Use full Card composition.
Card.Header/Card.Title/Card.Description/Card.Content/Card.Footer. Don't dump everything inCard.Content. - Button has no `isPending`/`isLoading`. Compose with
SpinnerinsideButton+disabled; usedata-icon="inline-start"/inline-endonSpinnerfor correct spacing (import { Button },import { Spinner }). - `Tabs.Trigger` must be inside `Tabs.List`. Never render triggers directly in
Tabs. - `Avatar` always needs `Avatar.Fallback`. For when the image fails to load.
Use Components, Not Custom Markup → composition.md
- Use existing components before custom markup. Check if a component exists before writing a styled
div. - Callouts use `Alert`. Don't build custom styled divs.
- Empty states use `Empty`. Don't build custom empty state markup.
- Toast via `svelte-sonner`. Use
toast()fromsvelte-sonnerwith the Sonner component from your UI folder. - Use `Separator` instead of
<hr>or adivwith border-only classes. - Use `Skeleton` for loading placeholders. No custom
animate-pulsedivs. - Use `Badge` instead of custom styled spans.
Icons → icons.md
- Icons in `<Button>` use `data-icon`.
data-icon="inline-start"ordata-icon="inline-end"on the icon. - No sizing classes on icons inside components. Components handle icon sizing via CSS. No
size-4orw-4 h-4. - Pass icons as components. Import from the configured
iconLibrary(e.g.@lucide/svelte), not string keys.
CLI
- Presets — copy the encoded string from the design-system builder on shadcn-svelte.com and pass it to
npx shadcn-svelte@latest init --preset <code>.
Key Patterns
These are the most common patterns that differentiate correct shadcn-svelte code. For edge cases, see the linked rule files above.
<script lang="ts">
import * as Field from "$lib/components/ui/field";
import { Input } from "$lib/components/ui/input";
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
import { Badge } from "$lib/components/ui/badge";
import * as Avatar from "$lib/components/ui/avatar";
</script>
<!-- Form layout: Field.FieldGroup + Field.Field, not div + Label. -->
<Field.FieldGroup>
<Field.Field>
<Field.FieldLabel for="email">Email</Field.FieldLabel>
<Input id="email" />
</Field.Field>
</Field.FieldGroup>
<!-- Validation: data-invalid on Field, aria-invalid on the control. -->
<Field.Field data-invalid>
<Field.FieldLabel for="email">Email</Field.FieldLabel>
<Input id="email" aria-invalid />
<Field.FieldDescription>Invalid email.</Field.FieldDescription>
</Field.Field>
<!-- Icons in buttons: data-icon, no sizing classes. -->
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
<!-- Spacing: gap-*, not space-y-*. -->
<div class="flex flex-col gap-4"></div>
<!-- Equal dimensions: size-*, not w-* h-*. -->
<Avatar.Root class="size-10">
<Avatar.Image src="/u.png" alt="User" />
<Avatar.Fallback>U</Avatar.Fallback>
</Avatar.Root>
<!-- Status colors: Badge variants or semantic tokens, not raw colors. -->
<Badge variant="secondary">+20.1%</Badge>Component Selection
| Need | Use |
|---|---|
| Button/action | Button with appropriate variant (import { Button }) |
| Form inputs | Input, Select, Combobox, Switch, Checkbox, RadioGroup, Textarea, InputOTP, Slider |
| Toggle between 2–5 options | ToggleGroup.Root + ToggleGroup.Item |
| Data display | Table, Card, Badge, Avatar |
| Navigation | Sidebar, NavigationMenu, Breadcrumb, Tabs, Pagination |
| Overlays | Dialog (modal), Sheet (side panel), Drawer (bottom sheet), AlertDialog (confirmation) |
| Feedback | svelte-sonner (toast), Alert, Progress, Skeleton, Spinner |
| Command palette | Command inside Dialog |
| Charts | Chart (LayerChart) |
| Layout | Card, Separator, Resizable, ScrollArea, Accordion, Collapsible |
| Empty states | Empty |
| Menus | DropdownMenu, ContextMenu, Menubar |
| Tooltips/info | Tooltip, HoverCard, Popover |
Key Fields
Use components.json and the filesystem — not a separate info command:
- `aliases` → use the actual alias prefix from config (e.g.
$lib/), never hardcode unrelated projects. - `tailwind.css` → the global CSS file where theme variables live. Edit this file for theme tweaks; don't add a second globals file unless the user already uses one.
- `style` → visual treatment (e.g.
nova,vega, …) and registry style path. - `iconLibrary` → determines icon packages (
@lucide/svelte,@tabler/icons-svelte, etc.). Never assume@lucide/svelte. - `registry` → where the CLI fetches components; default official registry at
shadcn-svelte.com. - `resolvedPaths` (conceptual) → the CLI resolves
aliasesto absolute paths; listaliases.uion disk to see installed components.
See cli.md for commands and flags.
Component Docs, Examples, and Usage
Open https://shadcn-svelte.com/docs/components/<name>.md for docs and examples. When creating, fixing, debugging, or using a component, read the official page first so you follow the documented APIs.
Workflow
1. Get project context — read components.json and list the UI components directory when needed. 2. Check installed components first — before running add, list files under the resolved ui path. Don't import components that haven't been added, and don't re-add ones already present unless updating. 3. Discover components — npx shadcn-svelte@latest add with no arguments (interactive list), or the docs site. 4. Install or update — npx shadcn-svelte@latest add <name> or a registry URL. To refresh existing files from the registry, use npx shadcn-svelte@latest update (see cli.md). 5. Fix imports in third-party / URL-added items — After adding from a custom registry URL, check for hardcoded paths that don't match the project's aliases. Rewrite imports to use the project's ui / lib aliases from components.json. 6. Review added components — After adding, read the added files and verify composition (groups, titles, validation attrs). Align icon imports with iconLibrary. 7. Remote registry items — Adding by URL is explicit; if the user wants a component from an unknown source, confirm the registry URL or item before running add.
Updating Components
Use the `update` command to pull the latest registry versions of components already in the project. Review changes with git diff after update.
1. Commit or stash local work. 2. Run npx shadcn-svelte@latest update [component] or --all. 3. Resolve merge conflicts if you had customized files. 4. Never use `--overwrite` on `add` without the user's explicit approval when it would destroy intentional edits.
Quick Reference
# Initialize shadcn-svelte in your project.
npx shadcn-svelte@latest init
# Initialize with a preset string from the docs site builder.
npx shadcn-svelte@latest init --preset <code>
# Add components (interactive when run with no names).
npx shadcn-svelte@latest add
npx shadcn-svelte@latest add button card dialog
npx shadcn-svelte@latest add --all
# Update components already installed.
npx shadcn-svelte@latest update button
npx shadcn-svelte@latest update --all --yes
# Build a custom registry (registry authors).
npx shadcn-svelte@latest registry buildRegistry: default https://shadcn-svelte.com/registry — override in components.json if needed. Docs: shadcn-svelte.com
Detailed References
- rules/forms.md — Field.FieldGroup, Field.Field, InputGroup, ToggleGroup, Field.FieldSet, validation states
- rules/composition.md — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
- rules/icons.md — data-icon, icon sizing, passing icon components
- rules/styling.md — Semantic colors, variants, class, spacing, size, truncate, dark mode, cn(), z-index
- cli.md — Commands, flags, registry
- customization.md — Theming, CSS variables, extending components
interface:
display_name: "shadcn-svelte"
short_description: "Manages shadcn-svelte components — adding, updating, fixing, debugging, styling, and composing UI."
icon_small: "./assets/shadcn-svelte-small.png"
icon_large: "./assets/shadcn-svelte.png"
shadcn-svelte CLI Reference
Configuration is read from components.json. See components.json on the docs site for the full schema.
IMPORTANT: Always run commands using the project's package runner:npx shadcn-svelte@latest,pnpm dlx shadcn-svelte@latest, orbunx --bun shadcn-svelte@latest. CheckpackageManagerfrom the project (or lockfile) to choose the right one. Examples below usenpx shadcn-svelte@latestbut substitute the correct runner for the project.
IMPORTANT: Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager; there is no --package-manager flag.Contents
- Commands:
init,add,update,registry build - Proxy / outgoing requests
- Presets (via
init)
---
Commands
init — Initialize an existing project
npx shadcn-svelte@latest init [options]Installs dependencies, adds the cn util, creates components.json, and sets up CSS variables. Run init from the root of your project.
| Flag | Short | Description | Default |
|---|---|---|---|
--preset <preset> | — | Encoded design-system preset string from the docs site | — |
-c, --cwd <path> | -c | Working directory | current |
-o, --overwrite | — | Overwrite existing files | false |
--no-deps | — | Do not add or install dependencies | — |
--skip-preflight | — | Ignore preflight checks and continue | false |
--base-color <name> | — | Base color: neutral, stone, zinc, mauve, olive, mist, taupe | — |
--css <path> | — | Path to the global CSS file | — |
--components-alias <path> | — | Import alias for components | — |
--lib-alias <path> | — | Import alias for lib | — |
--utils-alias <path> | — | Import alias for utils | — |
--hooks-alias <path> | — | Import alias for hooks | — |
--ui-alias <path> | — | Import alias for UI components | — |
--proxy <proxy> | — | Fetch registry items through this proxy | env-based |
--design-system-url | — | Optional design-system URL (see docs / preset builder) | — |
-h, --help | -h | Help | — |
---
add — Add components
npx shadcn-svelte@latest add [options] [components...]Adds components from the configured registry. Arguments are component names from the registry index, or a URL to a registry JSON item. With no component names, the CLI prompts you to pick components interactively.
| Flag | Short | Description | Default |
|---|---|---|---|
-c, --cwd <path> | -c | Working directory | current |
--no-deps | — | Skip adding and installing package dependencies | — |
--skip-preflight | — | Ignore preflight checks and continue | false |
-a, --all | — | Install all UI components | false |
-y, --yes | — | Skip confirmation prompt | false |
-o, --overwrite | — | Overwrite existing files | false |
--proxy <proxy> | — | Fetch components through this proxy | env-based |
-h, --help | -h | Help | — |
---
update — Update installed components
npx shadcn-svelte@latest update [options] [components...]Re-fetches and applies registry content for components already present in the project. Run shadcn-svelte update --help for options.
| Flag | Short | Description | Default |
|---|---|---|---|
-c, --cwd <path> | -c | Working directory | current |
--skip-preflight | — | Ignore preflight checks and continue | false |
--no-deps | — | Skip adding and installing package dependencies | — |
-a, --all | — | Update every installed component | false |
-y, --yes | — | Skip confirmation prompt | false |
--proxy <proxy> | — | Fetch through this proxy | env-based |
-h, --help | -h | Help | — |
Commit your work before updating; overwrites are destructive.
---
registry build — Build a custom registry
npx shadcn-svelte@latest registry build [options] [registry]Reads a registry.json and writes registry JSON files for distribution. Default input: ./registry.json, default output: ./static/r.
| Flag | Short | Description | Default |
|---|---|---|---|
-c, --cwd <path> | -c | Working directory | current |
-o, --output <path> | -o | Output directory for JSON files | ./static/r |
-h, --help | -h | Help | — |
---
Outgoing Requests
Proxy
The CLI can fetch the registry through a proxy. If HTTP_PROXY or http_proxy is set, requests respect it. You can also pass --proxy on init, add, or update.
HTTP_PROXY="<proxy-url>" npx shadcn-svelte@latest init---
Presets
Design-system options (style, theme, icons, fonts, etc.) can be captured as an encoded preset string from the builder on shadcn-svelte.com. Pass it to `init` with --preset <string>.
Changing presets on an existing project: re-run `init` with the new preset (and confirm overwrites when prompted), or edit components.json and CSS and then run add / update as needed.
---
components.json — useful fields for agents
| Field / path | Meaning |
|---|---|
tailwind.css | Global CSS file path (Tailwind entry / theme variables) |
tailwind.baseColor | Base palette (cannot change after init) |
aliases.* | Import aliases; must match svelte.config.js / tsconfig paths |
registry | Base registry URL (default https://shadcn-svelte.com/registry) |
style | Registered style name (e.g. nova, vega, …) |
iconLibrary | Icon set key (lucide, tabler, …) — drives generated imports |
typescript | Whether TS and optional custom config path |
Resolved paths (including tailwindCss, ui, components) are computed by the CLI from components.json and the filesystem. Read components.json and list the UI directory when you need a snapshot of what is installed.
Customization & Theming
Components reference semantic CSS variable tokens. Change the variables to change every component.
Contents
- How it works (CSS variables → Tailwind utilities → components)
- Color variables and OKLCH format
- Dark mode setup
- Changing the theme (presets, CSS variables)
- Adding custom colors (Tailwind v3 and v4)
- Border radius
- Customizing components (variants, class, wrappers)
- Checking for updates
---
How It Works
1. CSS variables defined in :root (light) and .dark (dark mode). 2. Tailwind maps them to utilities: bg-primary, text-muted-foreground, etc. 3. Components use these utilities — changing a variable changes all components that reference it.
---
Color Variables
Every color follows the name / name-foreground convention. The base variable is for backgrounds, -foreground is for text/icons on that background.
| Variable | Purpose |
|---|---|
--background / --foreground | Page background and default text |
--card / --card-foreground | Card surfaces |
--primary / --primary-foreground | Primary buttons and actions |
--secondary / --secondary-foreground | Secondary actions |
--muted / --muted-foreground | Muted/disabled states |
--accent / --accent-foreground | Hover and accent states |
--destructive / --destructive-foreground | Error and destructive actions |
--border | Default border color |
--input | Form input borders |
--ring | Focus ring color |
--chart-1 through --chart-5 | Chart/data visualization |
--sidebar-* | Sidebar-specific colors |
--surface / --surface-foreground | Secondary surface |
Colors use OKLCH: --primary: oklch(0.205 0 0) where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
---
Dark Mode
Class-based toggle via .dark on the root element. In SvelteKit, use mode-watcher (see Dark mode — Svelte):
<script lang="ts">
import { ModeWatcher } from "mode-watcher";
let { children } = $props();
</script>
<ModeWatcher />
{@render children?.()}---
Changing the Theme
Use a preset from the design-system builder on shadcn-svelte.com and pass it to init:
npx shadcn-svelte@latest init --preset <code>Or edit CSS variables directly in the file set in components.json as tailwind.css (for example src/app.css).
To align config and components with a new preset, re-run init with --preset and confirm overwrites when prompted.
---
Adding Custom Colors
Add variables to the global CSS file path in components.json (tailwind.css). Do not create a second global CSS file for theming unless the project already uses that pattern.
/* 1. Define in the global CSS file. */
:root {
--warning: oklch(0.84 0.16 84);
--warning-foreground: oklch(0.28 0.07 46);
}
.dark {
--warning: oklch(0.41 0.11 46);
--warning-foreground: oklch(0.99 0.02 95);
}/* 2a. Register with Tailwind v4 (@theme inline). */
@theme inline {
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}On Tailwind v3, register in tailwind.config.js (see the Tailwind v3 docs if you maintain a legacy setup):
// 2b. Register with Tailwind v3 (tailwind.config.js).
module.exports = {
theme: {
extend: {
colors: {
warning: "oklch(var(--warning) / <alpha-value>)",
"warning-foreground":
"oklch(var(--warning-foreground) / <alpha-value>)",
},
},
},
};<!-- 3. Use in components. -->
<div class="bg-warning text-warning-foreground">Warning</div>---
Border Radius
--radius controls border radius globally. Components derive values from it (rounded-lg = var(--radius), rounded-md = calc(var(--radius) - 2px)).
---
Customizing Components
See also: rules/styling.md for Incorrect/Correct examples.
Prefer these approaches in order:
1. Built-in variants
<script lang="ts">
import { Button } from "$lib/components/ui/button";
</script>
<Button variant="outline" size="sm">Click</Button>2. Tailwind classes via class
<script lang="ts">
import * as Card from "$lib/components/ui/card";
</script>
<Card.Root class="mx-auto max-w-md">
<Card.Content>...</Card.Content>
</Card.Root>3. Add a new variant
Edit the component source to add a variant via tailwind-variants / cva in the .svelte or shared variants file:
// e.g. in button variants
warning: "bg-warning text-warning-foreground hover:bg-warning/90",4. Wrapper components
Compose shadcn-svelte primitives into higher-level .svelte files:
<script lang="ts">
import * as AlertDialog from "$lib/components/ui/alert-dialog";
let { title, description, onConfirm, children } = $props();
let open = $state(false);
</script>
<AlertDialog.Root bind:open>
<AlertDialog.Trigger>
{@render children?.()}
</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>{title}</AlertDialog.Title>
<AlertDialog.Description>{description}</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Action
onclick={() => {
onConfirm?.();
open = false;
}}>Confirm</AlertDialog.Action
>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>---
Checking for Updates
npx shadcn-svelte@latest update button
npx shadcn-svelte@latest update --allSee Updating Components in SKILL.md. Review git diff after update to see what changed.
{
"skill_name": "shadcn-svelte",
"evals": [
{
"id": 1,
"prompt": "I'm building a SvelteKit app with shadcn-svelte (nova style, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
"expected_output": "A Svelte component using Field.FieldGroup, Field.Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
"files": [],
"expectations": [
"Uses Field.FieldGroup and Field.Field for form layout instead of raw div with space-y",
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
"No manual dark: color overrides"
]
},
{
"id": 2,
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn-svelte with tabler icons.",
"expected_output": "A Svelte component with Dialog.Title, Avatar with Avatar.Fallback, data-icon on icon buttons, no icon sizing classes, @tabler/icons-svelte imports.",
"files": [],
"expectations": [
"Includes Dialog.Title for accessibility (visible or with sr-only class)",
"Avatar includes Avatar.Fallback",
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
"Uses tabler icons (@tabler/icons-svelte) instead of @lucide/svelte when tabler is configured",
"Uses shadcn-svelte Dialog patterns (e.g. Dialog.Trigger wrapping the control, or bind:open on Dialog.Root)"
]
},
{
"id": 3,
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn-svelte with lucide icons.",
"expected_output": "A Svelte component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
"files": [],
"expectations": [
"Uses full Card composition with Card.Header, Card.Title, Card.Content (not dumping everything into Card.Content)",
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
"Uses Badge component for percentage change instead of custom styled spans",
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
"Uses gap-* instead of space-y-* or space-x-* for spacing",
"Uses size-* when width and height are equal instead of separate w-* h-*"
]
}
]
}
Component Composition
Contents
- Items always inside their Group component
- Callouts use Alert
- Empty states use Empty component
- Toast notifications use svelte-sonner
- Choosing between overlay components
- Dialog, Sheet, and Drawer always need a Title
- Card structure
- Button has no isPending or isLoading prop
- Tabs.Trigger must be inside Tabs.List
- Avatar always needs Avatar.Fallback
- Use Separator instead of raw hr or border divs
- Use Skeleton for loading placeholders
- Use Badge instead of custom styled spans
---
Items always inside their Group component
Never render items directly inside the content container.
Incorrect:
<script lang="ts">
import * as Select from "$lib/components/ui/select";
</script>
<Select.Content>
<Select.Item value="apple">Apple</Select.Item>
<Select.Item value="banana">Banana</Select.Item>
</Select.Content>Correct:
<script lang="ts">
import * as Select from "$lib/components/ui/select";
</script>
<Select.Content>
<Select.Group>
<Select.Item value="apple">Apple</Select.Item>
<Select.Item value="banana">Banana</Select.Item>
</Select.Group>
</Select.Content>This applies to all group-based components:
| Item | Group |
|---|---|
Select.Item, Select.Label | Select.Group |
DropdownMenu.Item, DropdownMenu.Label, DropdownMenu.Sub | DropdownMenu.Group |
Menubar.Item | Menubar.Group |
ContextMenu.Item | ContextMenu.Group |
Command.Item | Command.Group |
---
Callouts use Alert
<script lang="ts">
import * as Alert from "$lib/components/ui/alert";
</script>
<Alert.Root>
<Alert.Title>Warning</Alert.Title>
<Alert.Description>Something needs attention.</Alert.Description>
</Alert.Root>---
Empty states use Empty component
<script lang="ts">
import * as Empty from "$lib/components/ui/empty";
import { Button } from "$lib/components/ui/button";
import FolderIcon from "@lucide/svelte/icons/folder";
</script>
<Empty.Root>
<Empty.Header>
<Empty.Media variant="icon"><FolderIcon /></Empty.Media>
<Empty.Title>No projects yet</Empty.Title>
<Empty.Description
>Get started by creating a new project.</Empty.Description
>
</Empty.Header>
<Empty.Content>
<Button>Create Project</Button>
</Empty.Content>
</Empty.Root>---
Toast notifications use svelte-sonner
<script lang="ts">
import { toast } from "svelte-sonner";
</script>toast.success("Changes saved.");
toast.error("Something went wrong.");
toast("File deleted.", {
action: { label: "Undo", onClick: () => undoDelete() },
});Mount the Toaster from your UI folder once in the app layout (see Sonner).
---
Choosing between overlay components
| Use case | Component |
|---|---|
| Focused task that requires input | Dialog |
| Destructive action confirmation | AlertDialog |
| Side panel with details or filters | Sheet |
| Mobile-first bottom panel | Drawer |
| Quick info on hover | HoverCard |
| Small contextual content on click | Popover |
---
Dialog, Sheet, and Drawer always need a Title
Dialog.Title, Sheet.Title, Drawer.Title are required for accessibility. Use class="sr-only" if visually hidden.
<script lang="ts">
import * as Dialog from "$lib/components/ui/dialog";
</script>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Edit Profile</Dialog.Title>
<Dialog.Description>Update your profile.</Dialog.Description>
</Dialog.Header>
...
</Dialog.Content>---
Card structure
Use full composition — don't dump everything into Card.Content:
<script lang="ts">
import * as Card from "$lib/components/ui/card";
import { Button } from "$lib/components/ui/button";
</script>
<Card.Root>
<Card.Header>
<Card.Title>Team Members</Card.Title>
<Card.Description>Manage your team.</Card.Description>
</Card.Header>
<Card.Content>...</Card.Content>
<Card.Footer>
<Button>Invite</Button>
</Card.Footer>
</Card.Root>---
Button has no isPending or isLoading prop
Compose with Spinner inside Button + disabled:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import { Spinner } from "$lib/components/ui/spinner";
</script>
<Button disabled>
<Spinner data-icon="inline-start" />
Saving...
</Button>---
Tabs.Trigger must be inside Tabs.List
Never render Tabs.Trigger directly inside Tabs.Root — always wrap in Tabs.List:
<script lang="ts">
import * as Tabs from "$lib/components/ui/tabs";
let tab = $state("account");
</script>
<Tabs.Root bind:value={tab}>
<Tabs.List>
<Tabs.Trigger value="account">Account</Tabs.Trigger>
<Tabs.Trigger value="password">Password</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="account">...</Tabs.Content>
</Tabs.Root>---
Avatar always needs Avatar.Fallback
Always include Avatar.Fallback for when the image fails to load:
<script lang="ts">
import * as Avatar from "$lib/components/ui/avatar";
</script>
<Avatar.Root>
<Avatar.Image src="/avatar.png" alt="User" />
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar.Root>---
Use existing components instead of custom markup
| Instead of | Use |
|---|---|
<hr> or <div class="border-t"> | <Separator /> (import { Separator } from "$lib/components/ui/separator") |
<div class="animate-pulse"> with styled divs | <Skeleton class="h-4 w-3/4" /> (import { Skeleton } from "$lib/components/ui/skeleton") |
<span class="rounded-full bg-green-100 ..."> | <Badge variant="secondary"> (import { Badge } from "$lib/components/ui/badge") |
Forms & Inputs
Contents
- Forms use Field.FieldGroup + Field.Field
- InputGroup requires InputGroup.Input/InputGroup.Textarea
- Buttons inside inputs use InputGroup.Root + InputGroup.Addon
- Option sets (2–7 choices) use ToggleGroup.Root + ToggleGroup.Item
- Field.FieldSet + Field.FieldLegend for grouping related fields
- Field validation and disabled states
---
Forms use Field.FieldGroup + Field.Field
Always use Field.FieldGroup + Field.Field — never raw div with space-y-*:
<script lang="ts">
import * as Field from "$lib/components/ui/field";
import { Input } from "$lib/components/ui/input";
</script>
<Field.FieldGroup>
<Field.Field>
<Field.FieldLabel for="email">Email</Field.FieldLabel>
<Input id="email" type="email" />
</Field.Field>
<Field.Field>
<Field.FieldLabel for="password">Password</Field.FieldLabel>
<Input id="password" type="password" />
</Field.Field>
</Field.FieldGroup>Use Field with orientation="horizontal" for settings pages. Use Field.FieldLabel with class="sr-only" for visually hidden labels.
Choosing form controls:
- Simple text input →
Input - Dropdown with predefined options →
Select - Searchable dropdown →
Combobox - Native HTML select (no JS) →
native-select - Boolean toggle →
Switch(for settings) orCheckbox(for forms) - Single choice from few options →
RadioGroup - Toggle between 2–5 options →
ToggleGroup.Root+ToggleGroup.Item - OTP/verification code →
InputOTP - Multi-line text →
Textarea
---
InputGroup requires InputGroup.Input/InputGroup.Textarea
Never use raw Input or Textarea inside an InputGroup.Root.
Incorrect:
<script lang="ts">
import * as InputGroup from "$lib/components/ui/input-group";
import { Input } from "$lib/components/ui/input";
</script>
<InputGroup.Root>
<Input placeholder="Search..." />
</InputGroup.Root>Correct:
<script lang="ts">
import * as InputGroup from "$lib/components/ui/input-group";
</script>
<InputGroup.Root>
<InputGroup.Input placeholder="Search..." />
</InputGroup.Root>---
Buttons inside inputs use InputGroup.Root + InputGroup.Addon
Never place a Button directly inside or adjacent to an Input with custom positioning.
Incorrect:
<script lang="ts">
import { Input } from "$lib/components/ui/input";
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
</script>
<div class="relative">
<Input placeholder="Search..." class="pr-10" />
<Button class="absolute top-0 right-0" size="icon">
<SearchIcon />
</Button>
</div>Correct:
<script lang="ts">
import * as InputGroup from "$lib/components/ui/input-group";
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
</script>
<InputGroup.Root>
<InputGroup.Input placeholder="Search..." />
<InputGroup.Addon>
<Button size="icon">
<SearchIcon data-icon="inline-start" />
</Button>
</InputGroup.Addon>
</InputGroup.Root>---
Option sets (2–7 choices) use ToggleGroup.Root + ToggleGroup.Item
Don't manually loop Button components with active state.
Incorrect:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
let selected = $state("daily");
</script>
<div class="flex gap-2">
{#each ["daily", "weekly", "monthly"] as option (option)}
<Button
variant={selected === option ? "default" : "outline"}
onclick={() => (selected = option)}
>
{option}
</Button>
{/each}
</div>Correct:
<script lang="ts">
import * as ToggleGroup from "$lib/components/ui/toggle-group";
let selected = $state("daily");
</script>
<ToggleGroup.Root bind:value={selected} spacing={2}>
<ToggleGroup.Item value="daily">Daily</ToggleGroup.Item>
<ToggleGroup.Item value="weekly">Weekly</ToggleGroup.Item>
<ToggleGroup.Item value="monthly">Monthly</ToggleGroup.Item>
</ToggleGroup.Root>Combine with Field for labelled toggle groups:
<script lang="ts">
import * as Field from "$lib/components/ui/field";
import * as ToggleGroup from "$lib/components/ui/toggle-group";
</script>
<Field.Field orientation="horizontal">
<Field.FieldTitle id="theme-label">Theme</Field.FieldTitle>
<ToggleGroup.Root aria-labelledby="theme-label" spacing={2}>
<ToggleGroup.Item value="light">Light</ToggleGroup.Item>
<ToggleGroup.Item value="dark">Dark</ToggleGroup.Item>
<ToggleGroup.Item value="system">System</ToggleGroup.Item>
</ToggleGroup.Root>
</Field.Field>---
Field.FieldSet + Field.FieldLegend for grouping related fields
Use Field.FieldSet + Field.FieldLegend for related checkboxes, radios, or switches — not div with a heading:
<script lang="ts">
import * as Field from "$lib/components/ui/field";
import { Checkbox } from "$lib/components/ui/checkbox";
</script>
<Field.FieldSet>
<Field.FieldLegend variant="label">Preferences</Field.FieldLegend>
<Field.FieldDescription>Select all that apply.</Field.FieldDescription>
<Field.FieldGroup class="gap-3">
<Field.Field orientation="horizontal">
<Checkbox id="dark" />
<Field.FieldLabel for="dark" class="font-normal"
>Dark mode</Field.FieldLabel
>
</Field.Field>
</Field.FieldGroup>
</Field.FieldSet>---
Field validation and disabled states
Both attributes are needed — data-invalid/data-disabled styles the field (label, description), while aria-invalid/disabled styles the control.
<script lang="ts">
import * as Field from "$lib/components/ui/field";
import { Input } from "$lib/components/ui/input";
</script>
<!-- Invalid. -->
<Field.Field data-invalid>
<Field.FieldLabel for="email">Email</Field.FieldLabel>
<Input id="email" aria-invalid />
<Field.FieldDescription>Invalid email address.</Field.FieldDescription>
</Field.Field>
<!-- Disabled. -->
<Field.Field data-disabled>
<Field.FieldLabel for="email">Email</Field.FieldLabel>
<Input id="email" disabled />
</Field.Field>Works for all controls: Input, Textarea, Select, Checkbox, RadioGroupItem, Switch, Slider, NativeSelect, InputOTP.
Icons
Always use the project's configured `iconLibrary` for imports. Check the iconLibrary field in components.json: lucide → @lucide/svelte, tabler → @tabler/icons-svelte, etc. Never assume @lucide/svelte.
---
Icons in Button use data-icon attribute
Add data-icon="inline-start" (prefix) or data-icon="inline-end" (suffix) to the icon. No sizing classes on the icon.
Incorrect:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
</script>
<Button>
<SearchIcon class="mr-2 size-4" />
Search
</Button>Correct:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
import ArrowRightIcon from "@lucide/svelte/icons/arrow-right";
</script>
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
<Button>
Next
<ArrowRightIcon data-icon="inline-end" />
</Button>---
No sizing classes on icons inside components
Components handle icon sizing via CSS. Don't add size-4, w-4 h-4, or other sizing classes to icons inside <Button>, DropdownMenu.Item, Alert.Root, Sidebar.*, or other shadcn-svelte components — unless the user explicitly asks for custom icon sizes.
Incorrect:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
</script>
<Button>
<SearchIcon class="size-4" data-icon="inline-start" />
Search
</Button>Correct:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import SearchIcon from "@lucide/svelte/icons/search";
</script>
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>The same applies to icons inside DropdownMenu.Item, sidebar items, and other menu rows — no extra sizing classes on the icon component.
---
Pass icons as components, not string keys
Use a component reference, not a string key to a lookup map.
Incorrect:
<!-- String key lookup — avoid -->
<DynamicIcon name="check" />Correct:
<script lang="ts">
import type { Component } from "svelte";
import CheckIcon from "@lucide/svelte/icons/check";
let { Icon }: { Icon: Component } = $props();
</script>
<Icon />
<!-- <StatusBadge Icon={CheckIcon} /> -->Styling & Customization
See customization.md for theming, CSS variables, and adding custom colors.
Contents
- Semantic colors
- Built-in variants first
- class for layout only
- No space-x-_ / space-y-_
- Prefer size-_ over w-_ h-\* when equal
- Prefer truncate shorthand
- No manual dark: color overrides
- Use cn() for conditional classes
- No manual z-index on overlay components
---
Semantic colors
Incorrect:
<div class="bg-blue-500 text-white">
<p class="text-gray-600">Secondary text</p>
</div>Correct:
<div class="bg-primary text-primary-foreground">
<p class="text-muted-foreground">Secondary text</p>
</div>---
No raw color values for status/state indicators
For positive, negative, or status indicators, use Badge variants, semantic tokens like text-destructive, or define custom CSS variables — don't reach for raw Tailwind colors.
Incorrect:
<span class="text-emerald-600">+20.1%</span>
<span class="text-green-500">Active</span>
<span class="text-red-600">-3.2%</span>Correct:
<script lang="ts">
import { Badge } from "$lib/components/ui/badge";
</script>
<Badge variant="secondary">+20.1%</Badge>
<Badge>Active</Badge>
<span class="text-destructive">-3.2%</span>If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see customization.md).
---
Built-in variants first
Incorrect:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
</script>
<Button class="border-input hover:bg-accent border bg-transparent"
>Click me</Button
>Correct:
<script lang="ts">
import { Button } from "$lib/components/ui/button";
</script>
<Button variant="outline">Click me</Button>---
class for layout only
Use class for layout (e.g. max-w-md, mx-auto, mt-4), not for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
Incorrect:
<script lang="ts">
import * as Card from "$lib/components/ui/card";
</script>
<Card.Root class="bg-blue-100 font-bold text-blue-900">
<Card.Content>Dashboard</Card.Content>
</Card.Root>Correct:
<script lang="ts">
import * as Card from "$lib/components/ui/card";
</script>
<Card.Root class="mx-auto max-w-md">
<Card.Content>Dashboard</Card.Content>
</Card.Root>To customize a component's appearance, prefer these approaches in order:
1. Built-in variants — variant="outline", variant="destructive", etc. 2. Semantic color tokens — bg-primary, text-muted-foreground. 3. CSS variables — define custom colors in the global CSS file (see customization.md).
---
No space-x-_ / space-y-_
Use gap-* instead. space-y-4 → flex flex-col gap-4. space-x-2 → flex gap-2.
<script lang="ts">
import { Input } from "$lib/components/ui/input";
import { Button } from "$lib/components/ui/button";
</script>
<div class="flex flex-col gap-4">
<Input />
<Input />
<Button>Submit</Button>
</div>---
Prefer size-_ over w-_ h-\* when equal
size-10 not w-10 h-10. Applies to icons, avatars, skeletons, etc.
---
Prefer truncate shorthand
truncate not overflow-hidden text-ellipsis whitespace-nowrap.
---
No manual dark: color overrides
Use semantic tokens — they handle light/dark via CSS variables. bg-background text-foreground not bg-white dark:bg-gray-950.
---
Use cn() for conditional classes
Use the cn() utility from the project for conditional or merged class names. Don't write manual ternaries in class strings.
Incorrect:
<script lang="ts">
let isActive = $state(false);
</script>
<div class={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>Correct:
<script lang="ts">
import { cn } from "$lib/utils";
let isActive = $state(false);
</script>
<div class={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>---
No manual z-index on overlay components
Dialog, Sheet, Drawer, AlertDialog, DropdownMenu, Popover, Tooltip, HoverCard handle their own stacking. Never add z-50 or z-[999].
Related skills
How it compares
Choose shadcn-svelte over generic Svelte UI guidance when the project already uses components.json and copied shadcn-svelte registry components.
FAQ
What does shadcn-svelte do?
Manages shadcn-svelte components and projects — adding, updating, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with
When should I use shadcn-svelte?
During build frontend work for frontend development.
Is shadcn-svelte safe to install?
Review the Security Audits panel on this listing before production use.