From 936ee754b14a3a140d481fe338e2e1a6a8ee7d38 Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 6 Mar 2026 17:03:27 +0400 Subject: [PATCH] chore: update skills --- .gitignore | 1 + skills/shadcn/SKILL.md | 114 ++++++++++++++---- skills/shadcn/evals/evals.json | 47 ++++++++ skills/shadcn/rules/composition.md | 182 +++++++++++++++-------------- skills/shadcn/rules/forms.md | 100 ++++++++++------ skills/shadcn/rules/icons.md | 34 ++++++ skills/shadcn/rules/styling.md | 94 +++++++++++++++ 7 files changed, 425 insertions(+), 147 deletions(-) create mode 100644 skills/shadcn/evals/evals.json diff --git a/.gitignore b/.gitignore index aecf372c10..c040854c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ tsconfig.tsbuildinfo .notes .playwright-mcp +shadcn-workspace diff --git a/skills/shadcn/SKILL.md b/skills/shadcn/SKILL.md index 1f457a90d0..022142ad53 100644 --- a/skills/shadcn/SKILL.md +++ b/skills/shadcn/SKILL.md @@ -4,11 +4,6 @@ description: Manages shadcn components and projects — adding, searching, fixin user-invocable: false --- -# shadcn CLI (OVERRIDE) 🚨 - -> **IMPORTANT:** The shadcn CLI is currently in RC mode. The exact command prefix is `shadcn@rc`. -> For all the commands below, replace `shadcn@latest` with `shadcn@rc`. - # shadcn/ui A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI. @@ -34,20 +29,93 @@ The JSON above contains the project config and installed components. Use `npx sh These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs. -1. **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. → [forms.md](./rules/forms.md) -2. **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. → [composition.md](./rules/composition.md) -3. **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon. No sizing classes. → [icons.md](./rules/icons.md) -4. **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. → [forms.md](./rules/forms.md) -5. **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. → [forms.md](./rules/forms.md) -6. **Callouts use `Alert`.** Don't build custom styled divs. → [composition.md](./rules/composition.md) -7. **Empty states use `Empty`.** Don't build custom empty state markup. → [composition.md](./rules/composition.md) -8. **Use existing components before custom markup.** Check if a component exists before writing a styled `div`. -9. **`className` for layout, not styling.** Never override component colors or typography. → [styling.md](./rules/styling.md) -10. **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) -11. **Toast via `sonner`.** Use `toast()` from `sonner`. → [composition.md](./rules/composition.md) -12. **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. → [icons.md](./rules/icons.md) -13. **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** → [forms.md](./rules/forms.md) -14. **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest init --preset `. +### Styling & Tailwind → [styling.md](./rules/styling.md) + +- **`className` 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 `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. +- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. +- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** +- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. +- **`FieldSet` + `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.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. +- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) +- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden. +- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`. +- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`. +- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`. +- **`Avatar` always needs `AvatarFallback`.** 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 `sonner`.** Use `toast()` from `sonner`. +- **Use `Separator`** instead of `
` or `
`. +- **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 objects, not string keys.** `icon={CheckIcon}`, not a string lookup. + +### CLI + +- **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest init --preset `. + +## Key Patterns + +These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above. + +```tsx +// Form layout: FieldGroup + Field, not div + Label. + + + Email + + + + +// Validation: data-invalid on Field, aria-invalid on the control. + + Email + + Invalid email. + + +// Icons in buttons: data-icon, no sizing classes. + + +// Spacing: gap-*, not space-y-*. +
// correct +
// wrong + +// Equal dimensions: size-*, not w-* h-*. + // correct + // wrong + +// Status colors: Badge variants or semantic tokens, not raw colors. ++20.1% // correct ++20.1% // wrong +``` ## Component Selection @@ -163,10 +231,10 @@ npx shadcn@latest view @shadcn/button ## Detailed References -- [rules/forms.md](./rules/forms.md) — Form layout, InputGroup, ToggleGroup examples -- [rules/composition.md](./rules/composition.md) — Groups, Alert, Empty, Toast, Overlays -- [rules/icons.md](./rules/icons.md) — data-icon, passing icons as objects -- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className usage +- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, 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 icons as objects +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index - [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion - [cli.md](./cli.md) — Commands, flags, presets, templates - [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/skills/shadcn/evals/evals.json b/skills/shadcn/evals/evals.json new file mode 100644 index 0000000000..0df77e4b9b --- /dev/null +++ b/skills/shadcn/evals/evals.json @@ -0,0 +1,47 @@ +{ + "skill_name": "shadcn", + "evals": [ + { + "id": 1, + "prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, 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 React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.", + "files": [], + "expectations": [ + "Uses FieldGroup and Field components 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/ui with radix-nova preset and tabler icons.", + "expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.", + "files": [], + "expectations": [ + "Includes DialogTitle for accessibility (visible or with sr-only class)", + "Avatar component includes AvatarFallback", + "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-react) instead of lucide-react", + "Uses asChild for custom triggers (radix preset)" + ] + }, + { + "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/ui with base-nova preset and lucide icons.", + "expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.", + "files": [], + "expectations": [ + "Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)", + "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-*" + ] + } + ] +} diff --git a/skills/shadcn/rules/composition.md b/skills/shadcn/rules/composition.md index 199a4d9039..0e105837cb 100644 --- a/skills/shadcn/rules/composition.md +++ b/skills/shadcn/rules/composition.md @@ -7,6 +7,14 @@ - Empty states use Empty component - Toast notifications use sonner - Choosing between overlay components +- Dialog, Sheet, and Drawer always need a Title +- Card structure +- Button has no isPending or isLoading prop +- TabsTrigger must be inside TabsList +- Avatar always needs AvatarFallback +- Use Separator instead of raw hr or border divs +- Use Skeleton for loading placeholders +- Use Badge instead of custom styled spans --- @@ -48,55 +56,18 @@ This applies to all group-based components: ## Callouts use Alert -Don't build custom styled `div` containers for info/warning messages. - -**Incorrect:** - ```tsx -
-

Warning

-

Something needs attention.

-
-``` - -**Correct:** - -```tsx -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" - Warning Something needs attention. - - - Error - Something went wrong. - ``` --- ## Empty states use Empty component -Don't build custom empty state markup. - -**Incorrect:** - ```tsx -
- -

No projects yet

-

Get started by creating a new project.

- -
-``` - -**Correct:** - -```tsx -import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty" - @@ -113,22 +84,6 @@ import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTi ## Toast notifications use sonner -Don't build custom toast/notification markup. - -**Incorrect:** - -```tsx -const [showToast, setShowToast] = useState(false) - -{showToast && ( -
- Changes saved. -
-)} -``` - -**Correct:** - ```tsx import { toast } from "sonner" @@ -143,40 +98,6 @@ toast("File deleted.", { ## Choosing between overlay components -Don't default to `Dialog` for everything. **When recommending an overlay, always show the full component structure with all required subcomponents.** - -**Incorrect:** - -```tsx - - Delete - -

Are you sure?

- -
-
-``` - -**Correct:** - -```tsx - - Delete - - - Are you sure? - This action cannot be undone. - - - Cancel - Delete - - - -``` - -**Quick reference:** - | Use case | Component | |----------|-----------| | Focused task that requires input | `Dialog` | @@ -185,3 +106,90 @@ Don't default to `Dialog` for everything. **When recommending an overlay, always | Mobile-first bottom panel | `Drawer` | | Quick info on hover | `HoverCard` | | Small contextual content on click | `Popover` | + +--- + +## Dialog, Sheet, and Drawer always need a Title + +`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden. + +```tsx + + + Edit Profile + Update your profile. + + ... + +``` + +--- + +## Card structure + +Use full composition — don't dump everything into `CardContent`: + +```tsx + + + Team Members + Manage your team. + + ... + + + + +``` + +--- + +## Button has no isPending or isLoading prop + +Compose with `Spinner` + `data-icon` + `disabled`: + +```tsx + +``` + +--- + +## TabsTrigger must be inside TabsList + +Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`: + +```tsx + + + Account + Password + + ... + +``` + +--- + +## Avatar always needs AvatarFallback + +Always include `AvatarFallback` for when the image fails to load: + +```tsx + + + JD + +``` + +--- + +## Use existing components instead of custom markup + +| Instead of | Use | +|---|---| +| `
` or `
` | `` | +| `
` with styled divs | `` | +| `` | `` | diff --git a/skills/shadcn/rules/forms.md b/skills/shadcn/rules/forms.md index 0e229c75d1..f451e2f7bc 100644 --- a/skills/shadcn/rules/forms.md +++ b/skills/shadcn/rules/forms.md @@ -1,50 +1,34 @@ # Forms & Inputs +## Contents + +- Forms use FieldGroup + Field +- InputGroup requires InputGroupInput/InputGroupTextarea +- Buttons inside inputs use InputGroup + InputGroupAddon +- Option sets (2–7 choices) use ToggleGroup +- FieldSet + FieldLegend for grouping related fields +- Field validation and disabled states + --- ## Forms use FieldGroup + Field -Always use `FieldGroup` and `Field` to structure forms. Never use raw `div` with `grid`/`gap` or `space-y-*` for form layout. - -**Incorrect:** +Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`: ```tsx -
-
-
- - -
-
- - -
- -
-
+ + + Email + + + + Password + + + ``` -**Correct:** - -```tsx -import { Field, FieldDescription, FieldGroup, FieldLabel, FieldTitle } from "@/components/ui/field" - -
- - - Email - - - - Password - - - - -
-``` - -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. +Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels. **Choosing form controls:** @@ -164,3 +148,45 @@ Combine with `Field` for labelled toggle groups: ``` > **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup). + +--- + +## FieldSet + FieldLegend for grouping related fields + +Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading: + +```tsx +
+ Preferences + Select all that apply. + + + + Dark mode + + +
+``` + +--- + +## 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. + +```tsx +// Invalid. + + Email + + Invalid email address. + + +// Disabled. + + Email + + +``` + +Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`. diff --git a/skills/shadcn/rules/icons.md b/skills/shadcn/rules/icons.md index 3ab69a4aef..bba8102f01 100644 --- a/skills/shadcn/rules/icons.md +++ b/skills/shadcn/rules/icons.md @@ -33,6 +33,40 @@ Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to --- +## 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`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes. + +**Incorrect:** + +```tsx + + + + + Settings + +``` + +**Correct:** + +```tsx + + + + + Settings + +``` + +--- + ## Pass icons as component objects, not string keys Use `icon={CheckIcon}`, not a string key to a lookup map. diff --git a/skills/shadcn/rules/styling.md b/skills/shadcn/rules/styling.md index a8e717346b..38d473212b 100644 --- a/skills/shadcn/rules/styling.md +++ b/skills/shadcn/rules/styling.md @@ -2,6 +2,18 @@ See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors. +## Contents + +- Semantic colors +- Built-in variants first +- className 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 @@ -24,6 +36,30 @@ See [customization.md](../customization.md) for theming, CSS variables, and addi --- +## 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:** + +```tsx ++20.1% +Active +-3.2% +``` + +**Correct:** + +```tsx ++20.1% +Active +-3.2% +``` + +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:** @@ -66,3 +102,61 @@ 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`. + +```tsx +
+ + + +
+``` + +--- + +## 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 className strings. + +**Incorrect:** + +```tsx +
+``` + +**Correct:** + +```tsx +import { cn } from "@/lib/utils" + +
+``` + +--- + +## 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]`.