responsive ui + wallet redesign + 401 fix + round cards
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
---
|
||||
name: shadcn-svelte
|
||||
description: 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, the CLI, design-system presets, or any project with a components.json file. Also triggers for "shadcn-svelte init", "add component", or registry URLs.
|
||||
user-invocable: false
|
||||
allowed-tools: Bash(npx shadcn-svelte@latest *), Bash(pnpm dlx shadcn-svelte@latest *), Bash(bunx --bun shadcn-svelte@latest *)
|
||||
---
|
||||
|
||||
# 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's package manager. Examples below use `npx shadcn-svelte@latest` but 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](https://shadcn-svelte.com/docs/installation):
|
||||
|
||||
- **Multi-part components** (dialog, select, card, field, tabs, …): `import * as Dialog from "$lib/components/ui/dialog"` then `Dialog.Content`, `Dialog.Title`, `Card.Root`, `Card.Header`, etc. — whatever the barrel exports (short names and/or `Root as …` aliases).
|
||||
- **Single-component barrels** (only one meaningful component in the folder): **named imports** — `import { Button } from "$lib/components/ui/button"` and `<Button>`, not `import * as Button` + `Button.Root`. Same pattern for `{ Input }`, `{ Badge }`, `{ Spinner }`, `{ Checkbox }`, `{ Separator }`, `{ Skeleton }`, etc.
|
||||
|
||||
```ts
|
||||
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](https://shadcn-svelte.com/docs/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](./rules/styling.md)
|
||||
|
||||
- **`class` for layout, not styling.** Never override component colors or typography.
|
||||
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
||||
- **Use `size-*` when width and height are equal.** `size-10` not `w-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](./rules/forms.md)
|
||||
|
||||
- **Forms use `Field.FieldGroup` + `Field.Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
||||
- **`InputGroup` uses `InputGroup.Input`/`InputGroup.Textarea`.** Never raw `Input`/`Textarea` inside `InputGroup.Root`.
|
||||
- **Buttons inside inputs use `InputGroup.Root` + `InputGroup.Addon`.**
|
||||
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
||||
- **`Field.FieldSet` + `Field.FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
||||
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
||||
|
||||
### Component Structure → [composition.md](./rules/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 with `bind:open` on the root — see component docs.
|
||||
- **Dialog, Sheet, and Drawer always need a Title.** `Dialog.Title`, `Sheet.Title`, `Drawer.Title` required for accessibility. Use `class="sr-only"` if visually hidden.
|
||||
- **Use full Card composition.** `Card.Header`/`Card.Title`/`Card.Description`/`Card.Content`/`Card.Footer`. Don't dump everything in `Card.Content`.
|
||||
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` inside `Button` + `disabled`; use `data-icon="inline-start"` / `inline-end` on `Spinner` for 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](./rules/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()` from `svelte-sonner` with the Sonner component from your UI folder.
|
||||
- **Use `Separator`** instead of `<hr>` or a `div` with border-only classes.
|
||||
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||
- **Use `Badge`** instead of custom styled spans.
|
||||
|
||||
### Icons → [icons.md](./rules/icons.md)
|
||||
|
||||
- **Icons in `<Button>` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
||||
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-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](https://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.
|
||||
|
||||
```svelte
|
||||
<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 `aliases` to absolute paths; list `aliases.ui` on disk to see installed components.
|
||||
|
||||
See [cli.md](./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](./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
|
||||
|
||||
```bash
|
||||
# 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 build
|
||||
```
|
||||
|
||||
**Registry:** default `https://shadcn-svelte.com/registry` — override in `components.json` if needed.
|
||||
**Docs:** [shadcn-svelte.com](https://shadcn-svelte.com)
|
||||
|
||||
## Detailed References
|
||||
|
||||
- [rules/forms.md](./rules/forms.md) — Field.FieldGroup, Field.Field, InputGroup, ToggleGroup, Field.FieldSet, validation states
|
||||
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
|
||||
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icon components
|
||||
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, class, spacing, size, truncate, dark mode, cn(), z-index
|
||||
- [cli.md](./cli.md) — Commands, flags, registry
|
||||
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
||||
@@ -0,0 +1,5 @@
|
||||
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"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,166 @@
|
||||
# shadcn-svelte CLI Reference
|
||||
|
||||
Configuration is read from `components.json`. See [components.json](https://shadcn-svelte.com/docs/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`, or `bunx --bun shadcn-svelte@latest`. Check `packageManager` from the project (or lockfile) to choose the right one. Examples below use `npx shadcn-svelte@latest` but 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`, `apply`, `update`, `registry build`
|
||||
- Proxy / outgoing requests
|
||||
- Presets (via `init` and `apply`)
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### `init` — Initialize an existing project
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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 | — |
|
||||
|
||||
---
|
||||
|
||||
### `apply` — Apply a preset to an existing project
|
||||
|
||||
```bash
|
||||
npx shadcn-svelte@latest apply [options]
|
||||
```
|
||||
|
||||
Applies a design-system preset to a project that has already been initialized. Updates `components.json` with the preset settings, reinstalls existing components (except `utils`) with the new styles, and installs any required dependencies.
|
||||
|
||||
Use `--only theme` or `--only font` to apply only part of a preset without reinstalling UI components.
|
||||
|
||||
Get a preset code from the builder at [shadcn-svelte.com/create](https://shadcn-svelte.com/create).
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------------- | ----- | ---------------------------------------------- | --------- |
|
||||
| `--preset <preset>` | — | Encoded design-system preset string (required) | — |
|
||||
| `--only [parts]` | — | Apply only `theme` or `font` from the preset | — |
|
||||
| `-c, --cwd <path>` | `-c` | Working directory | current |
|
||||
| `-y, --yes` | `-y` | Overwrite existing files without confirmation | `false` |
|
||||
| `-s, --silent` | `-s` | Mute output | `false` |
|
||||
| `--skip-preflight` | — | Ignore preflight checks and continue | `false` |
|
||||
| `--proxy <proxy>` | — | Fetch registry items through this proxy | env-based |
|
||||
| `-h, --help` | `-h` | Help | — |
|
||||
|
||||
Requires an existing `components.json`. Run `init` first if the project is not yet configured.
|
||||
|
||||
---
|
||||
|
||||
### `update` — Update installed components
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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`, `apply`, or `update`.
|
||||
|
||||
```bash
|
||||
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/create](https://shadcn-svelte.com/create).
|
||||
|
||||
- **New project:** pass the preset to **`init`** with `--preset <string>`.
|
||||
- **Existing project:** use **`apply --preset <string>`** to update configuration, restyle installed components, and install any new dependencies.
|
||||
|
||||
---
|
||||
|
||||
## `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.
|
||||
@@ -0,0 +1,213 @@
|
||||
# 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](https://github.com/svecosystem/mode-watcher) (see [Dark mode — Svelte](https://shadcn-svelte.com/docs/dark-mode/svelte)):
|
||||
|
||||
```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](https://shadcn-svelte.com) and pass it to `init`:
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```css
|
||||
/* 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);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* 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](https://tw3.shadcn-svelte.com) if you maintain a legacy setup):
|
||||
|
||||
```js
|
||||
// 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>)",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```svelte
|
||||
<!-- 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](./rules/styling.md) for Incorrect/Correct examples.
|
||||
|
||||
Prefer these approaches in order:
|
||||
|
||||
### 1. Built-in variants
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
</script>
|
||||
|
||||
<Button variant="outline" size="sm">Click</Button>
|
||||
```
|
||||
|
||||
### 2. Tailwind classes via `class`
|
||||
|
||||
```svelte
|
||||
<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:
|
||||
|
||||
```ts
|
||||
// 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:
|
||||
|
||||
```svelte
|
||||
<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
|
||||
|
||||
```bash
|
||||
npx shadcn-svelte@latest update button
|
||||
npx shadcn-svelte@latest update --all
|
||||
```
|
||||
|
||||
See [Updating Components in SKILL.md](./SKILL.md#updating-components). Review `git diff` after `update` to see what changed.
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"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-*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
# 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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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
|
||||
|
||||
```svelte
|
||||
<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
|
||||
|
||||
```svelte
|
||||
<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
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { toast } from "svelte-sonner";
|
||||
</script>
|
||||
```
|
||||
|
||||
```ts
|
||||
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](https://shadcn-svelte.com/docs/components/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.
|
||||
|
||||
```svelte
|
||||
<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`:
|
||||
|
||||
```svelte
|
||||
<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`:
|
||||
|
||||
```svelte
|
||||
<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`:
|
||||
|
||||
```svelte
|
||||
<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:
|
||||
|
||||
```svelte
|
||||
<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"`) |
|
||||
@@ -0,0 +1,234 @@
|
||||
# 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-*`:
|
||||
|
||||
```svelte
|
||||
<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) or `Checkbox` (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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:
|
||||
|
||||
```svelte
|
||||
<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:
|
||||
|
||||
```svelte
|
||||
<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.
|
||||
|
||||
```svelte
|
||||
<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`.
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<!-- String key lookup — avoid -->
|
||||
<DynamicIcon name="check" />
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```svelte
|
||||
<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} /> -->
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
# Styling & Customization
|
||||
|
||||
See [customization.md](../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:**
|
||||
|
||||
```svelte
|
||||
<div class="bg-blue-500 text-white">
|
||||
<p class="text-gray-600">Secondary text</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<span class="text-emerald-600">+20.1%</span>
|
||||
<span class="text-green-500">Active</span>
|
||||
<span class="text-red-600">-3.2%</span>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```svelte
|
||||
<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](../customization.md)).
|
||||
|
||||
---
|
||||
|
||||
## Built-in variants first
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<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](../customization.md)).
|
||||
|
||||
---
|
||||
|
||||
## No space-x-_ / space-y-_
|
||||
|
||||
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
||||
|
||||
```svelte
|
||||
<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:**
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let isActive = $state(false);
|
||||
</script>
|
||||
|
||||
<div class={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```svelte
|
||||
<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]`.
|
||||
Reference in New Issue
Block a user