diff --git a/skills/shadcn/SKILL.md b/skills/shadcn/SKILL.md index 97cd4eae48..f46c3f7603 100644 --- a/skills/shadcn/SKILL.md +++ b/skills/shadcn/SKILL.md @@ -42,6 +42,7 @@ These rules are **always enforced**. See [patterns.md](./patterns.md) for full e 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. +14. **Never decode or fetch preset codes manually.** Preset codes are opaque. Pass them directly to `shadcn init --preset ` and let the CLI handle resolution. ## Key Fields @@ -79,7 +80,10 @@ shadcn docs button dialog select 5. **Install or update** — MCP `shadcn:get_add_command_for_items` or `shadcn add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below). 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 ` 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. +8. **Switching presets** — Ask the user first: **reinstall**, **merge**, or **skip**? + - **Reinstall**: `shadcn init --preset --force --reinstall`. Overwrites all components. + - **Merge**: `shadcn init --preset --force --no-reinstall`, then `shadcn info` to get installed components, then [smart merge](#updating-components) each one. + - **Skip**: `shadcn init --preset --force --no-reinstall`. Only updates config and CSS, leaves components as-is. If MCP is unavailable, use CLI: `shadcn search`, `shadcn view`, `shadcn add`. @@ -140,5 +144,6 @@ shadcn view @shadcn/button - [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 +- [base-patterns.md](./base-patterns.md) — API differences between base and radix (asChild vs render, Select items, ToggleGroup type, Slider values, Accordion) - [customization.md](./customization.md) — Theming, CSS variables, extending components - [registry-authoring.md](./registry-authoring.md) — Building and publishing custom registries diff --git a/skills/shadcn/base-patterns.md b/skills/shadcn/base-patterns.md new file mode 100644 index 0000000000..d8ac5f6661 --- /dev/null +++ b/skills/shadcn/base-patterns.md @@ -0,0 +1,230 @@ +# Base-specific patterns + +API differences between `base` and `radix`. Check the `base` field from `shadcn info` to determine which patterns to use. + +## Contents + +- Composition: asChild vs render +- Button / trigger as non-button element +- Select +- ToggleGroup +- Slider +- Accordion + +--- + +## Composition: asChild (radix) vs render (base) + +Radix uses `asChild` to replace the default element. Base uses `render`. + +```tsx +// radix. + + + + +// base. +}>Open +``` + +This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`. + +--- + +## Button / trigger as non-button element (base only) + +When `render` changes an element to a non-button (``, ``), add `nativeButton={false}`. + +```tsx +// base — button as link. + + +// radix equivalent. + +``` + +Same for triggers whose `render` is not a `Button`: + +```tsx +// base. +} nativeButton={false}> + Pick date + +``` + +--- + +## Select + +**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only. + +```tsx +// base. +const items = [ + { label: "Select a fruit", value: null }, + { label: "Apple", value: "apple" }, + { label: "Banana", value: "banana" }, +] + + + +// radix. + +``` + +**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses ``. + +**Multiple selection and object values (base only).** Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only. + +```tsx +// base — multiple selection. + + +// base — object values. + +``` + +**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`. + +```tsx +// base. + + +// radix. + +``` + +--- + +## ToggleGroup + +Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`. + +```tsx +// base — single (no prop needed), defaultValue is always an array. + + Daily + Weekly + + +// base — multi-selection. + + Bold + Italic + + +// radix — single, defaultValue is a string. + + Daily + Weekly + + +// radix — multi-selection. + + Bold + Italic + +``` + +**Controlled single value.** Base wraps/unwraps arrays. Radix uses a plain string. + +```tsx +// base. +const [value, setValue] = React.useState("normal") + setValue(v[0])}> + +// radix. +const [value, setValue] = React.useState("normal") + +``` + +--- + +## Slider + +Base accepts a plain number for a single thumb. Radix always requires an array. + +```tsx +// base. + + +// radix. + +``` + +Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast. + +```tsx +// base. +const [value, setValue] = React.useState([0.3, 0.7]) + setValue(v as number[])} /> + +// radix. +const [value, setValue] = React.useState([0.3, 0.7]) + +``` + +--- + +## Accordion + +Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. + +```tsx +// radix. + + ... + +``` + +Base uses no `type` prop. Use `multiple` for multi-select. `defaultValue` is an array. + +```tsx +// base. + + ... + + +// base — multi-select. + + ... + ... + +``` diff --git a/skills/shadcn/cli.md b/skills/shadcn/cli.md index 0ae5a37061..9799f8bc52 100644 --- a/skills/shadcn/cli.md +++ b/skills/shadcn/cli.md @@ -239,6 +239,8 @@ Three ways to specify a preset via `--preset`: 2. **Code:** `--preset a2r6bw` (base62 string, starts with lowercase `a`) 3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."` +> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `shadcn init --preset ` and let the CLI handle resolution. + ### Named Presets | Name | Base | Style | Font | Icon Library | @@ -266,11 +268,8 @@ Both use neutral base color, neutral theme, default radius, subtle menu accent, ## Switching Presets -To change an existing project's preset: +Ask the user first: **reinstall**, **merge**, or **skip** existing components? -```bash -shadcn init --preset a2r6bw --force -shadcn init --reinstall # optional: update existing components -``` - -Always confirm with the user before `--reinstall` — it overwrites component files. +- **Reinstall** → `shadcn init --preset --force --reinstall`. Overwrites all component files with the new preset styles. Use when the user hasn't customized components. +- **Merge** → `shadcn init --preset --force --no-reinstall`, then run `shadcn info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components. +- **Skip** → `shadcn init --preset --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is. diff --git a/skills/shadcn/patterns.md b/skills/shadcn/patterns.md index d4a992cd08..c33b692336 100644 --- a/skills/shadcn/patterns.md +++ b/skills/shadcn/patterns.md @@ -13,7 +13,8 @@ Rules and examples for composing shadcn/ui components. - Overlays - Empty states - Toast notifications -- Base-specific patterns + +**Base-conditional API differences**: See [base-patterns.md](./base-patterns.md) for `asChild` vs `render`, Select `items` prop, ToggleGroup `type` vs `multiple`, Slider value types, and Accordion differences. --- @@ -100,25 +101,15 @@ Use `Field orientation="horizontal"` for inline label + control layouts (e.g. se ## Toggle Groups -Use `ToggleGroup` + `ToggleGroupItem` when the user picks from a small set of options (2–7). Don't manually loop `Button` components with active state. +Use `ToggleGroup` + `ToggleGroupItem` when the user picks from a small set of options (2–7). Don't manually loop `Button` components with active state. See [base-patterns.md](./base-patterns.md#togglegroup) for `type` vs `multiple` prop differences. ```tsx import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" -// Single selection (e.g. schedule type). - + Daily Interval - -// Multi-selection (e.g. weekday picker). - - {["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((day) => ( - - {day} - - ))} - ``` Combine with `Field` for labelled toggle groups: @@ -126,7 +117,7 @@ Combine with `Field` for labelled toggle groups: ```tsx Theme - + Light Dark System @@ -134,6 +125,8 @@ Combine with `Field` for labelled toggle groups: ``` +> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-patterns.md](./base-patterns.md#togglegroup). + --- ## Alerts @@ -201,246 +194,6 @@ toast("File deleted.", { }) ``` - - --- -## Base-Specific Patterns - -Check the `base` field from `shadcn info` to determine which patterns to use. - -### Composition: asChild (radix) vs render (base) - -Radix uses `asChild` to replace the default element. Base UI uses `render` instead. - -```tsx -// radix. - - - - -// base. -}>Open -``` - -```tsx -// radix. - - - - -// base. -}> - Menu - -``` - -### Button as a link (base only) - -When `render` changes a `Button` to a non-button element (``, ``), add `nativeButton={false}` so Base UI doesn't apply button-specific behavior. - -```tsx -// base. - - -// radix equivalent. - -``` - -### Non-button trigger elements (base only) - -When a trigger's `render` is not a `Button` (e.g. `InputGroupAddon`), add `nativeButton={false}`. - -```tsx -// base. -} nativeButton={false}> - Pick date - -``` - -### Select (base vs radix) - -**items prop (base only).** Base `Select` requires an `items` prop on the root. Radix uses JSX only — no `items` prop. - -```tsx -// base. -const items = [ - { label: "Select a fruit", value: null }, - { label: "Apple", value: "apple" }, - { label: "Banana", value: "banana" }, -] - - - -// radix. - -``` - -**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses ``. - -**Multiple selection (base only).** Base supports `multiple` and render-function children on `SelectValue`. Radix has no multi-select. - -```tsx -// base. - -``` - -**Object values (base only).** Base supports object values with `itemToStringValue` and a render function on `SelectValue`. Radix uses string values only. - -```tsx -// base. - - -// radix — string values, controlled state. -const [plan, setPlan] = React.useState("starter") - -``` - -**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`. - -```tsx -// base. - - -// radix. - -``` - -### ToggleGroup: multiple (base) vs type (radix) - -Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`. - -```tsx -// base — single selection (no prop needed). - - Daily - Weekly - - -// base — multi-selection. - - Bold - Italic - - -// radix — single selection. - - Daily - Weekly - - -// radix — multi-selection. - - Bold - Italic - -``` - -**defaultValue / value type.** Base always uses arrays. Radix uses string for single, array for multiple. - -```tsx -// base — controlled single. -const [value, setValue] = React.useState("normal") - setValue(v[0])}> - -// radix — controlled single. -const [value, setValue] = React.useState("normal") - -``` - -### Slider: defaultValue (base vs radix) - -Base accepts a plain number for a single thumb. Radix always requires an array. - -```tsx -// base. - - -// radix. - -``` - -Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast. - -```tsx -// base. -const [value, setValue] = React.useState([0.3, 0.7]) - setValue(v as number[])} /> - -// radix. -const [value, setValue] = React.useState([0.3, 0.7]) - -``` - -### Accordion: type prop (radix only) - -Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. The `defaultValue` is a string. - -```tsx -// radix. - - ... - -``` - -Base uses no `type` prop. Use the `multiple` prop for multi-select. The `defaultValue` is an array. - -```tsx -// base. - - ... - - -// base multi-select. - - ... - ... - -``` - ---- - -For composition rules (asChild/render, className usage, Groups, data-icon, InputGroup, etc.), see the **Critical Rules** section in [SKILL.md](./SKILL.md#critical-rules). +For className usage, Groups, data-icon, InputGroup, and other composition rules, see the **Critical Rules** section in [SKILL.md](./SKILL.md#critical-rules). For asChild/render and other base-conditional APIs, see [base-patterns.md](./base-patterns.md).