feat: add shadcn skills

This commit is contained in:
shadcn
2026-02-26 22:17:32 +04:00
parent 8af3cfd031
commit b9b30a23e6
10 changed files with 1258 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add shadcn/skills

128
skills/shadcn/SKILL.md Normal file
View File

@@ -0,0 +1,128 @@
---
name: shadcn
description: Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
user-invocable: false
---
# shadcn/ui
Copy-paste component library. Components are added as source code to the user's project via the CLI or MCP tools.
> **IMPORTANT:** Always use the `shadcn` command directly. Never prefix with `npx`, `pnpm dlx`, or any package runner.
## Current Project Context
```json
!`shadcn info --json 2>/dev/null || echo '{"error": "No shadcn project found. Run shadcn init first."}'`
```
The JSON above contains the project config and installed components. Use `shadcn docs <component>` to get documentation and example URLs for any component.
## Principles
1. **Use existing components first.** Check registries via MCP or `shadcn search` before writing custom UI. Check community registries too.
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**. See [patterns.md](./patterns.md) for full examples.
1. **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
2. **Items always inside their Group.** `SelectItem`/`SelectLabel``SelectGroup`. `DropdownMenuItem`/`DropdownMenuLabel`/`DropdownMenuSub``DropdownMenuGroup`. `MenubarItem``MenubarGroup`. `ContextMenuItem``ContextMenuGroup`. `CommandItem``CommandGroup`.
3. **Icons in `Button` use `data-icon`.** Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix). No sizing classes on the icon.
4. **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never use raw `Input` or `Textarea` inside an `InputGroup`.
5. **Option sets (27 choices) use `ToggleGroup`.** Don't loop `Button` components with manual active state.
6. **Callouts use `Alert`.** Don't build custom styled `div` containers for info/warning messages.
7. **Empty states use `Empty`.** Don't build custom empty state markup.
8. **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
9. **`className` for layout, not styling.** Add layout utilities (`w-full`, `grid`, `flex`, `gap-*`) but never override component colors or typography.
10. **Use `asChild` (radix) or `render` (base) for custom triggers.** Don't wrap triggers in extra elements. Check the `base` field from `shadcn info` to determine which prop to use.
11. **Toast via `sonner`.** Use `toast()` from `sonner`. Don't build custom toast/notification markup.
12. **Pass icons as objects, not string keys.** Use `icon={CheckIcon}`, not a string key to a lookup map.
13. **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** Never place a `Button` directly inside or adjacent to an `Input` with custom positioning. Wrap in `InputGroup` and use `InputGroupAddon` for the button.
## Key Fields
The injected project context contains these key fields:
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"`.
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
See [cli.md — `info` command](./cli.md) for the full field reference.
## Component Docs, Examples, and Usage
Run `shadcn docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
```bash
shadcn docs button dialog select
```
**When creating, fixing, debugging, or using a component, always run `shadcn docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
## Workflow
1. **Get project context** — already injected above. Run `shadcn info` again if you need to refresh.
2. **Check installed components** — look in the `resolvedPaths.ui` directory before importing or adding. Don't import components that haven't been added, and don't re-add ones already installed.
3. **Find components** — MCP `shadcn:search_items_in_registries` or `shadcn search`.
4. **Get docs and examples** — run `shadcn docs <component>` to get URLs, then fetch them. Alternatively use MCP `shadcn:view_items_in_registries` or `shadcn view`.
5. **Install** — MCP `shadcn:get_add_command_for_items` or `shadcn add`.
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `shadcn info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
7. **Verify** — MCP `shadcn:get_audit_checklist`.
8. **Switching presets** — Before running `shadcn init --preset <code>` in an existing project, always ask the user if they'd like to reinstall existing UI components. If yes, use the `--reinstall` flag to overwrite them with the new preset styles.
If MCP is unavailable, use CLI: `shadcn search`, `shadcn view`, `shadcn add`.
## Quick Reference
```bash
# Create a new project.
shadcn init --name my-app --preset base-nova
shadcn init --name my-app --preset a2r6bw --template vite
# Create a monorepo project.
shadcn init --name my-app --preset base-nova --monorepo
shadcn init --name my-app --preset base-nova --template next --monorepo
# Initialize existing project.
shadcn init --preset base-nova
shadcn init --defaults # shortcut: --template=next --preset=base-nova
# Add components.
shadcn add button card dialog
shadcn add @magicui/shimmer-button
shadcn add --all
# Search registries.
shadcn search @shadcn -q "sidebar"
shadcn search @blocks -q "stats"
# Get component docs and example URLs.
shadcn docs button dialog select
# View item details.
shadcn view @shadcn/button
```
**Named presets:** `base-nova`, `radix-nova`
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`)
**Preset codes:** Base62 strings starting with `a` (e.g. `a2r6bw`), from [ui.shadcn.com](https://ui.shadcn.com).
## Detailed References
- [cli.md](./cli.md) — Commands, flags, presets, templates
- [mcp.md](./mcp.md) — MCP server setup, tools, registry configuration
- [patterns.md](./patterns.md) — UI patterns and component composition rules
- [customization.md](./customization.md) — Theming, CSS variables, extending components
- [registry-authoring.md](./registry-authoring.md) — Building and publishing custom registries

View File

@@ -0,0 +1,5 @@
interface:
display_name: "shadcn/ui"
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
icon_small: "./assets/shadcn-small.png"
icon_large: "./assets/shadcn.png"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

229
skills/shadcn/cli.md Normal file
View File

@@ -0,0 +1,229 @@
# shadcn CLI Reference
All commands use `shadcn <command>`. Configuration is read from `components.json`.
> **IMPORTANT:** Always use the `shadcn` command directly. Never prefix with `npx`, `pnpm dlx`, or any package runner.
---
## Commands
### `init` — Initialize or create a project
```bash
shadcn init [components...] [options]
```
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
| `--yes` | `-y` | Skip confirmation prompt | `true` |
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
| `--force` | `-f` | Force overwrite existing configuration | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--name <name>` | `-n` | Name for new project | — |
| `--silent` | `-s` | Mute output | `false` |
| `--rtl` | | Enable RTL support | — |
| `--reinstall` | | Re-install existing UI components | `false` |
| `--monorepo` | | Scaffold a monorepo project | — |
| `--no-monorepo` | | Skip the monorepo prompt | — |
`shadcn create` is an alias for `shadcn init`.
### `add` — Add components
```bash
shadcn add [components...] [options]
```
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`), URLs, or local paths.
| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
| `--overwrite` | `-o` | Overwrite existing files | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--all` | `-a` | Add all available components | `false` |
| `--path <path>` | `-p` | Target path for the component | — |
| `--silent` | `-s` | Mute output | `false` |
### `search` — Search registries
```bash
shadcn search <registries...> [options]
```
Fuzzy search across registries. Also aliased as `shadcn list`. Without `-q`, lists all items.
| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--query <query>` | `-q` | Search query | — |
| `--limit <number>` | `-l` | Max items per registry | `100` |
| `--offset <number>` | `-o` | Items to skip | `0` |
| `--cwd <cwd>` | `-c` | Working directory | current |
### `view` — View item details
```bash
shadcn view <items...> [options]
```
Displays item info including file contents. Example: `shadcn view @shadcn/button`.
### `docs` — Get component documentation URLs
```bash
shadcn docs <components...> [options]
```
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
Example output for `shadcn docs input button`:
```
base radix
input
docs https://ui.shadcn.com/docs/components/radix/input
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
button
docs https://ui.shadcn.com/docs/components/radix/button
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
```
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
### `diff` — Check for updates
```bash
shadcn diff [component] [options]
```
Compares local components against the registry. Without a component name, shows all with updates.
### `info` — Project information
```bash
shadcn info [options]
```
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--cwd <cwd>` | `-c` | Working directory | current |
**Project Info fields:**
| Field | Type | Meaning |
|-------|------|---------|
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
| `isRSC` | `boolean` | Whether React Server Components are enabled |
| `isTsx` | `boolean` | Whether the project uses TypeScript |
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
| `tailwindCssFile` | `string` | Path to the global CSS file |
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
**Components.json fields:**
| Field | Type | Meaning |
|-------|------|---------|
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
| `rsc` | `boolean` | RSC flag from config |
| `tsx` | `boolean` | TypeScript flag |
| `tailwind.config` | `string` | Tailwind config path |
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
| `registries` | `object` | Configured custom registries |
**Links fields:**
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `shadcn docs <component>` instead.
### `build` — Build a custom registry
```bash
shadcn build [registry] [options]
```
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--output <path>` | `-o` | Output directory | `./public/r` |
| `--cwd <cwd>` | `-c` | Working directory | current |
---
## Templates
| Value | Framework | Monorepo support |
|-------|-----------|-----------------|
| `next` | Next.js | Yes |
| `vite` | Vite | Yes |
| `start` | TanStack Start | Yes |
| `react-router` | React Router | Yes |
| `astro` | Astro | Yes |
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively.
---
## Presets
Three ways to specify a preset via `--preset`:
1. **Named:** `--preset base-nova` or `--preset radix-nova`
2. **Code:** `--preset a2r6bw` (base62 string, starts with lowercase `a`)
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
### Named Presets
| Name | Base | Style | Font | Icon Library |
|------|------|-------|------|-------------|
| `radix-nova` | radix | nova | geist | lucide |
| `base-nova` | base | nova | geist | lucide |
Both use neutral base color, neutral theme, default radius, subtle menu accent, default menu color.
### Preset Fields
| Field | Valid Values | Default |
|-------|-------------|---------|
| `base` | `radix`, `base` | `radix` |
| `style` | `nova`, `vega`, `maia`, `lyra`, `mira` | `nova` |
| `baseColor` | `neutral`, `stone`, `zinc`, `gray` | `neutral` |
| `theme` | `neutral`, `stone`, `zinc`, `gray`, `amber`, `blue`, `cyan`, `emerald`, `fuchsia`, `green`, `indigo`, `lime`, `orange`, `pink`, `purple`, `red`, `rose`, `sky`, `teal`, `violet`, `yellow` | `neutral` |
| `iconLibrary` | `lucide`, `hugeicons`, `tabler`, `phosphor`, `remixicon` | `lucide` |
| `font` | `inter`, `noto-sans`, `nunito-sans`, `figtree`, `roboto`, `raleway`, `dm-sans`, `public-sans`, `outfit`, `jetbrains-mono`, `geist`, `geist-mono` | `inter` |
| `radius` | `default`, `none`, `small`, `medium`, `large` | `default` |
| `menuAccent` | `subtle`, `bold` | `subtle` |
| `menuColor` | `default`, `inverted` | `default` |
---
## Switching Presets
To change an existing project's preset:
```bash
shadcn init --preset a2r6bw --force
shadcn init --reinstall # optional: update existing components
```
Always confirm with the user before `--reinstall` — it overwrites component files.

View File

@@ -0,0 +1,180 @@
# Customization & Theming
Components reference semantic CSS variable tokens. Change the variables to change every component.
---
## 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 (01), chroma (0 = gray), and hue (0360).
---
## Dark Mode
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
```tsx
import { ThemeProvider } from "next-themes"
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
```
---
## Changing the Theme
```bash
# Apply a preset code from ui.shadcn.com.
shadcn init --preset a2r6bw --force
# Switch to a named preset.
shadcn init --preset radix-nova --force
shadcn init --reinstall # update existing components to match
# Use a custom theme URL.
shadcn init --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..." --force
```
Or edit CSS variables directly in `globals.css`.
---
## Adding Custom Colors
Add variables to the file at `tailwindCssFile` from `shadcn info` (typically `globals.css`). Never create a new CSS file for this.
```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);
}
```
When `tailwindVersion` is `"v3"` (check via `shadcn info`), register in `tailwind.config.js` instead:
```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>)",
},
},
},
}
```
```tsx
// 3. Use in components.
<div className="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
Prefer these approaches in order:
### 1. Built-in variants
```tsx
<Button variant="outline" size="sm">Click</Button>
```
### 2. Tailwind classes via `className`
```tsx
<Card className="max-w-md mx-auto">...</Card>
```
### 3. Add a new variant
Edit the component source to add a variant via `cva`:
```tsx
// components/ui/button.tsx
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
```
### 4. Wrapper components
Compose shadcn/ui primitives into higher-level components:
```tsx
export function ConfirmDialog({ title, description, onConfirm, children }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
---
## Checking for Updates
```bash
shadcn diff # all components with available updates
shadcn diff button # specific component
```

94
skills/shadcn/mcp.md Normal file
View File

@@ -0,0 +1,94 @@
# shadcn MCP Server
The CLI includes an MCP server that lets AI assistants search, browse, view, and install components from registries.
---
## Setup
```bash
shadcn mcp # start the MCP server (stdio)
shadcn mcp init # write config for your editor
```
Editor config files:
| Editor | Config file |
|--------|------------|
| Claude Code | `.mcp.json` |
| Cursor | `.cursor/mcp.json` |
| VS Code | `.vscode/mcp.json` |
| OpenCode | `opencode.json` |
| Codex | `~/.codex/config.toml` (manual) |
---
## Tools
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `shadcn info` — there is no MCP equivalent.
### `shadcn:get_project_registries`
Returns registry names from `components.json`. Errors if no `components.json` exists.
**Input:** none
### `shadcn:list_items_in_registries`
Lists all items from one or more registries.
**Input:** `registries` (string[]), `limit` (number, optional), `offset` (number, optional)
### `shadcn:search_items_in_registries`
Fuzzy search across registries.
**Input:** `registries` (string[]), `query` (string), `limit` (number, optional), `offset` (number, optional)
### `shadcn:view_items_in_registries`
View item details including full file contents.
**Input:** `items` (string[]) — e.g. `["@shadcn/button", "@shadcn/card"]`
### `shadcn:get_item_examples_from_registries`
Find usage examples and demos with source code.
**Input:** `registries` (string[]), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
### `shadcn:get_add_command_for_items`
Returns the CLI install command.
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
### `shadcn:get_audit_checklist`
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
**Input:** none
---
## Configuring Registries
Registries are set in `components.json`. The `@shadcn` registry is always built-in.
```json
{
"registries": {
"@acme": "https://acme.com/r/{name}.json",
"@private": {
"url": "https://private.com/r/{name}.json",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
```
- Names must start with `@`.
- URLs must contain `{name}`.
- `${VAR}` references are resolved from environment variables.
Community registry index: `https://ui.shadcn.com/r/registries.json`

230
skills/shadcn/patterns.md Normal file
View File

@@ -0,0 +1,230 @@
# Component Patterns
Rules and examples for composing shadcn/ui components.
> **Note:** Examples below use `@/` as the import prefix. Always use the actual `aliasPrefix` from `shadcn info` for the target project. Similarly, icon imports depend on the project's `iconLibrary` — use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, `@phosphor-icons/react` for `phosphor`, etc. Never assume `lucide-react`.
---
## Component Selection
| Need | Use |
|------|-----|
| Button/action | `Button` with appropriate variant |
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
| Toggle between 25 options | `ToggleGroup` + `ToggleGroupItem` |
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
| Command palette | `Command` inside `Dialog` |
| Charts | `Chart` (wraps Recharts) |
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
| Empty states | `Empty` |
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
---
## Forms
Always use `FieldGroup` and `Field` to structure forms. Never use raw `div` with `grid`/`gap` or `space-y-*` for form layout. Use `FieldLabel` for labelled inputs, `FieldTitle` for section headings, and `FieldDescription` for helper text.
```tsx
import { Button } from "@/components/ui/button"
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldTitle } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
<form>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" placeholder="you@example.com" />
</Field>
<Field>
<FieldLabel htmlFor="password">Password</FieldLabel>
<Input id="password" type="password" />
</Field>
<Field>
<FieldTitle>Role</FieldTitle>
<FieldDescription>Select the user's role in the organization.</FieldDescription>
<Select>
<SelectTrigger className="w-full">
<SelectValue placeholder="Choose a role" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="bio">Bio</FieldLabel>
<Textarea id="bio" placeholder="Tell us about yourself" />
</Field>
<Button type="submit" className="w-full">Sign In</Button>
</FieldGroup>
</form>
```
Use `Field orientation="horizontal"` for inline label + control layouts (e.g. settings pages). Use `FieldLabel className="sr-only"` for inputs that don't need a visible label but still need one for accessibility.
**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 25 options → `ToggleGroup` + `ToggleGroupItem`
- OTP/verification code → `InputOTP`
- Multi-line text → `Textarea`
---
## Toggle Groups
Use `ToggleGroup` + `ToggleGroupItem` when the user picks from a small set of options (27). Don't manually loop `Button` components with active state.
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
// Single selection (e.g. schedule type).
<ToggleGroup defaultValue={["daily"]} spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="interval">Interval</ToggleGroupItem>
</ToggleGroup>
// Multi-selection (e.g. weekday picker).
<ToggleGroup value={activeDays} onValueChange={setActiveDays} spacing={2}>
{["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((day) => (
<ToggleGroupItem key={day} value={day} className="size-7 rounded-full text-xs">
{day}
</ToggleGroupItem>
))}
</ToggleGroup>
```
Combine with `Field` for labelled toggle groups:
```tsx
<Field orientation="horizontal">
<FieldTitle id="theme-label">Theme</FieldTitle>
<ToggleGroup aria-labelledby="theme-label" defaultValue={["system"]} spacing={2}>
<ToggleGroupItem value="light">Light</ToggleGroupItem>
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
<ToggleGroupItem value="system">System</ToggleGroupItem>
</ToggleGroup>
</Field>
```
---
## Alerts
Use `Alert` for informational callouts. Don't build custom styled `div` containers for info/warning messages.
```tsx
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
<Alert>
<AlertDescription>
Automations run with your default sandbox settings.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>Something went wrong.</AlertDescription>
</Alert>
```
---
## Overlays
- 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`
---
## Empty States
Use the `Empty` component when there's no data to display.
```tsx
import { Empty, EmptyIcon, EmptyTitle, EmptyDescription, EmptyActions } from "@/components/ui/empty"
<Empty>
<EmptyIcon>{/* icon */}</EmptyIcon>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
<EmptyActions>
<Button>Create Project</Button>
</EmptyActions>
</Empty>
```
---
## Toast Notifications
Use `sonner`. Call `toast()` from anywhere.
```tsx
import { toast } from "sonner"
toast.success("Changes saved.")
toast.error("Something went wrong.")
toast("File deleted.", {
action: { label: "Undo", onClick: () => undoDelete() },
})
```
---
## Composition Rules
1. **Use `asChild` (radix) or `render` (base) for custom triggers.** Don't wrap triggers in extra elements. Check the `base` field from `shadcn info` to determine which prop to use.
2. **Use `Field` components for forms, not raw divs.** Never use `div` with `space-y-*` or `grid gap-*` for form layout. Use `FieldGroup` for the form container and `Field` for each control. Use `FieldLabel` for labelled inputs, `FieldTitle` for headings, `FieldDescription` for helper text.
3. **Use existing UI components before custom markup.** Before writing a styled `div`, check if a component already exists: `Alert` for callouts, `ToggleGroup` for option sets, `Empty` for empty states, etc.
4. **Use `className` for layout, not styling.** Add layout utilities (`w-full`, `grid`, `flex`, `gap-*`) but avoid overriding component colors or typography.
5. **Prefer built-in variants.** Use `variant="outline"`, `variant="ghost"`, `size="sm"` etc. before adding custom classes.
6. **Compose, don't customize.** Build complex UIs by composing multiple simple components rather than heavily customizing a single one.
7. **Add `"use client"` at RSC boundaries.** When `isRSC` is `true` (from `shadcn info`), components that use `useState`, `useEffect`, event handlers (`onClick`, `onChange`, etc.), or browser APIs must start with `"use client"`. Keep client boundaries as low in the tree as possible.
8. **Keep components small and focused.** A component should render itself and its own interactions (e.g. a card + its dialog). Page-level layout, data arrays, and loops belong in the page, not the component.
9. **Pass data directly, not keys.** When passing icons or other references through props, pass the actual objects (e.g. `icon={ComputerTerminal01Icon}`), not string keys to a lookup map.
10. **Always wrap items and labels in a Group.** For menu-like components, items, labels, and subs must be direct children of their group component. Never place them directly inside the content container.
- `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` → inside `DropdownMenuGroup`
- `MenubarItem`, `MenubarLabel`, `MenubarSub` → inside `MenubarGroup`
- `ContextMenuItem`, `ContextMenuLabel`, `ContextMenuSub` → inside `ContextMenuGroup`
- `SelectItem`, `SelectLabel` → inside `SelectGroup`
- `CommandItem` → inside `CommandGroup`
11. **Always use `data-icon` on icons inside buttons.** When placing an icon inside a `Button`, add `data-icon="inline-start"` for prefix icons or `data-icon="inline-end"` for suffix icons. This ensures correct spacing. Never add sizing classes to the icon — the button handles sizing automatically.
12. **Always use `InputGroupInput` and `InputGroupTextarea` inside an `InputGroup`.** Never use the base `Input` or `Textarea` components directly inside an `InputGroup`. The input-group variants reset borders, backgrounds, and focus rings to compose correctly within the group.
13. **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** Never place a `Button` directly inside or adjacent to an `Input` with custom positioning. Wrap in `InputGroup` and use `InputGroupAddon` for the button.
14. **`InputGroupAddon` must use `InputGroupButton`.** Never place a raw `Button` inside `InputGroupAddon`. Always use `InputGroupButton` instead.
15. **`InputGroupButton` sizing depends on addon alignment.** When `InputGroupAddon` is `align="inline-start"` or `align="inline-end"`, prefer `size="icon-sm"` or `size="icon-xs"` (icon-only buttons). When `align="block-start"` or `align="block-end"`, prefer `size="xs"` or `size="sm"` (buttons with text).

View File

@@ -0,0 +1,387 @@
# Registry Authoring
Build and publish your own shadcn/ui-compatible component registry. Registries let you distribute reusable components, blocks, hooks, and utilities that others can install with a single command.
---
## Overview
A registry is a collection of items (components, blocks, hooks, etc.) described in a `registry.json` file. The `shadcn build` command compiles this into individual JSON files that can be hosted on any static file server.
```
my-registry/
├── registry.json # registry definition
├── registry/
│ ├── my-button.tsx # component source files
│ ├── my-card.tsx
│ └── hooks/
│ └── use-something.ts
└── public/r/ # built output (created by shadcn build)
├── my-button.json
├── my-card.json
└── use-something.json
```
---
## registry.json Format
The schema is available at `https://ui.shadcn.com/schema/registry.json`.
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "my-registry",
"homepage": "https://my-registry.dev",
"items": [
{
"name": "my-button",
"type": "registry:ui",
"title": "My Button",
"description": "A custom button with extra features.",
"dependencies": ["class-variance-authority"],
"registryDependencies": ["button"],
"files": [
{
"path": "registry/my-button.tsx",
"type": "registry:ui"
}
]
}
]
}
```
### Required Fields
| Field | Description |
|-------|-------------|
| `name` | Registry name (used in the registry index) |
| `homepage` | URL to the registry's homepage |
| `items` | Array of registry items |
### Item Fields
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Unique item name within the registry |
| `type` | Yes | Item type (see types below) |
| `title` | No | Human-readable display title |
| `description` | No | Brief description of the item |
| `author` | No | Author info (e.g., `"username <https://github.com/username>"`) |
| `dependencies` | No | NPM packages to install |
| `devDependencies` | No | NPM dev dependencies to install |
| `registryDependencies` | No | Other registry items this depends on |
| `files` | No | Array of source files |
| `cssVars` | No | CSS variables to inject (light, dark, theme) |
| `css` | No | Raw CSS rules to inject |
| `tailwind` | No | Tailwind config extensions |
| `envVars` | No | Environment variables (added to `.env`) |
| `docs` | No | Markdown documentation |
| `categories` | No | Array of category strings |
| `meta` | No | Arbitrary metadata object |
---
## Item Types
| Type | Purpose | Target Path |
|------|---------|-------------|
| `registry:ui` | UI primitives (button, card, etc.) | `components/ui/` |
| `registry:component` | Reusable composed components | `components/` |
| `registry:block` | Multi-file page blocks (login, dashboard) | `components/` |
| `registry:hook` | React hooks | `hooks/` |
| `registry:lib` | Utility functions | `lib/` |
| `registry:page` | Full pages (requires `target`) | Custom path |
| `registry:file` | Static files (requires `target`) | Custom path |
| `registry:theme` | Theme definitions | — |
| `registry:style` | Global style definitions | — |
| `registry:item` | Generic item | `components/` |
**Note:** `registry:page` and `registry:file` require a `target` field on each file to specify the output path.
---
## File Objects
Files can be specified in two ways:
```json
{
"files": [
{
"path": "registry/my-component.tsx",
"type": "registry:ui"
}
]
}
```
| Field | Required | Description |
|-------|----------|-------------|
| `path` | Yes | Path relative to the registry root |
| `type` | Yes | File type (same enum as item types) |
| `target` | Conditional | Output path in the project (required for `registry:file` and `registry:page`) |
| `content` | No | Inline file content (auto-read from `path` if not provided) |
---
## Dependencies
### NPM Dependencies
```json
{
"dependencies": ["framer-motion", "class-variance-authority"],
"devDependencies": ["@types/some-package"]
}
```
These are installed via the user's package manager when they `shadcn add` the item.
### Registry Dependencies
```json
{
"registryDependencies": ["button", "card", "https://other-registry.com/r/item.json"]
}
```
- **Plain names** (e.g., `"button"`) reference items from the `@shadcn` registry.
- **URLs** reference items from other registries.
- Registry dependencies are automatically resolved and installed.
---
## CSS Variables
Inject CSS variables into the user's project:
```json
{
"cssVars": {
"light": {
"chart-custom": "oklch(0.646 0.222 41.116)"
},
"dark": {
"chart-custom": "oklch(0.268 0.08 34.298)"
},
"theme": {
"--color-chart-custom": "var(--chart-custom)"
}
}
}
```
- `light` — Variables added to `:root`.
- `dark` — Variables added to `.dark`.
- `theme` — Variables added to `@theme inline` (Tailwind v4 only).
---
## CSS Rules
Inject raw CSS into the project:
```json
{
"css": {
"@keyframes shimmer": {
"from": { "background-position": "200% 0" },
"to": { "background-position": "-200% 0" }
},
".animate-shimmer": {
"animation": "shimmer 8s ease-in-out infinite",
"background-size": "200% 100%"
}
}
}
```
Supports at-rules, selectors, nested rules, utilities, and layers.
---
## Environment Variables
```json
{
"envVars": {
"DATABASE_URL": "The connection string for your database.",
"API_KEY": "Your API key from the dashboard."
}
}
```
These are added to the user's `.env` file with placeholder values.
---
## Building the Registry
```bash
shadcn build
```
This reads `registry.json` and outputs individual JSON files to `public/r/`:
```bash
# Custom input/output paths.
shadcn build ./path/to/registry.json --output ./public/r
```
Each item becomes a standalone JSON file (e.g., `public/r/my-button.json`) containing the resolved item with file contents inlined.
---
## Hosting
Host the built `public/r/` directory on any static file server. The URL pattern must include a `{name}` placeholder:
```
https://my-registry.dev/r/{name}.json
```
**Hosting options:**
- **Vercel/Netlify** — Deploy the project, files are served from `public/r/`.
- **GitHub Pages** — Push `public/r/` to a gh-pages branch.
- **Any CDN** — Upload the JSON files.
---
## Configuration for Users
Users add your registry to their `components.json`:
### Simple format
```json
{
"registries": {
"@myregistry": "https://my-registry.dev/r/{name}.json"
}
}
```
### With authentication
```json
{
"registries": {
"@myregistry": {
"url": "https://my-registry.dev/r/{name}.json",
"headers": {
"Authorization": "Bearer ${MY_REGISTRY_TOKEN}"
}
}
}
}
```
Environment variable references (`${VAR}`) are resolved at runtime.
### Installing items
```bash
shadcn add @myregistry/my-button
shadcn search @myregistry -q "button"
```
---
## Community Registry Index
To list your registry in the public directory at `https://ui.shadcn.com/r/registries.json`, submit it through the shadcn/ui repository.
Each entry in the index looks like:
```json
{
"name": "@myregistry",
"homepage": "https://my-registry.dev",
"url": "https://my-registry.dev/r/{name}.json",
"description": "A collection of custom components."
}
```
---
## Example: Minimal Registry
**registry.json:**
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme-ui",
"homepage": "https://acme-ui.dev",
"items": [
{
"name": "status-badge",
"type": "registry:ui",
"title": "Status Badge",
"description": "A badge that shows status with color coding.",
"registryDependencies": ["badge"],
"files": [
{
"path": "registry/status-badge.tsx",
"type": "registry:ui"
}
]
},
{
"name": "use-copy-to-clipboard",
"type": "registry:hook",
"title": "useCopyToClipboard",
"description": "A hook to copy text to the clipboard.",
"files": [
{
"path": "registry/hooks/use-copy-to-clipboard.ts",
"type": "registry:hook"
}
]
}
]
}
```
**Build and host:**
```bash
shadcn build
# Outputs: public/r/status-badge.json, public/r/use-copy-to-clipboard.json
```
**Users install with:**
```bash
shadcn add @acme-ui/status-badge
```
---
## Example: Block with Multiple Files
Blocks are multi-file items like dashboards or login forms:
```json
{
"name": "login-form",
"type": "registry:block",
"title": "Login Form",
"description": "A complete login form with email and password fields.",
"registryDependencies": ["button", "input", "label", "card"],
"files": [
{
"path": "registry/login-form/page.tsx",
"type": "registry:page",
"target": "app/login/page.tsx"
},
{
"path": "registry/login-form/login-form.tsx",
"type": "registry:component"
}
]
}
```