react-shadcn-ui
Grounded stack guidance: pull idiomatic Do/Don't + docs URLs via
ground.py search --manifest <skills-root>/design-intelligence/data/manifest.json --stack shadcn "<topic>"(also--stack react,--stack nextjs). Seedesign-intelligence.
Component installer — scripts/shadcn_add.py (gated, assisted)
Bundled installer (Apache-2.0-derived, see header + design-intelligence/ATTRIBUTION.md)
wraps npx shadcn@latest add <components> — the only subprocess+network
surface in the adopted suite. Per runtime-safety + the execution
block above:
- Propose, never silent-run — always show the exact
npxcommand + component list first (use--dry-run); the user confirms before any live run. - Missing tool → per
missing-tool-handling: ifnpx/Node is absent, STOP and ask (install vs. manual component copy) — never silently work around. - Verify after run — confirm the component landed
(
components/ui/<name>.tsxexists,components.jsonunchanged or sanely updated) before reporting success.
Compatibility
- Tested against:
shadcn@2.1, Tailwind CSS3.x, React18+. - The audit step (
directives/ui/audit.py) reads the line above and compares it withstate.ui_audit.shadcn_inventory.version; a major mismatch triggers a soft halt before this skill runs.
When to use
Use when state.stack.frontend == "react-shadcn" and directives/ui/apply.py,
review.py, or polish.py dispatches to this skill, or when a React project
clearly uses shadcn/ui (presence of components.json, @radix-ui/*
dependencies, a components/ui/ folder of generated primitives).
Do NOT use when:
- Project is Blade + Livewire + Flux (use
flux/livewire/blade-ui). - Project is Vue (use the Vue stack skills).
- Plain React without shadcn/ui — fall back to manual composition; this skill assumes the primitive set exists.
Gotcha
- shadcn/ui is not an npm package. Primitives are copied into
components/ui/and edited in-place. Do notnpm install shadcn-ui. Runnpx shadcn@latest add <primitive>to scaffold; then edit. - Major-version drift between this skill's
## Compatibilityline and the project's installed primitives is a real risk. The audit step writesstate.ui_audit.shadcn_inventorywith the detected version — when it diverges by a major, audit emits a soft halt before this skill runs. - shadcn/ui composes Radix primitives. Accessibility is built in via Radix
but only when you use the wrapper components correctly (
asChild,<DialogTrigger>instead of a bare<button>). - Tailwind tokens come from
tailwind.config.{js,ts}(theme.extend.colors) and CSS custom properties on:rootand.dark(--background,--foreground,--primary,--ring, …). Audit writes them intostate.ui_audit.design_tokens. Use those tokens; do not hardcode values. - Dark mode is class-based (
<html class="dark">). Every color must come frombg-background,text-foreground, etc. — never rawbg-white. - Every interactive primitive must declare a focus-visible state via
focus-visible:ring-2 focus-visible:ring-ring; that comes for free with the generated primitives but is easy to remove during a refactor. - Anti-AI-slop: shadcn-default look. The out-of-the-box shadcn
theme +
Inter-as-system-fallback + neutral grays reads as template across projects. Unlessstate.ui_audit.design_tokenspins the neutral palette as the project's identity, the polish step should match typography and color tokens to the design brief'saesthetic:line (fromfe-designaesthetic-direction). Theme/font drift within a single audited project breaks consistency — variation lives between projects, not between components in the same surface.
Covered primitives
This skill is validated against the following shadcn primitives at the declared version:
- Form / inputs:
Button,Input,Textarea,Checkbox,RadioGroup,Select,Switch,Label,Form(react-hook-form wrapper +zodResolver). - Overlay:
Dialog,Sheet,Popover,Tooltip,DropdownMenu,AlertDialog. - Layout:
Card,Separator,Tabs,Accordion,ScrollArea. - Data display:
Table(with@tanstack/react-table),Badge,Avatar,Skeleton,Progress. - Feedback:
Toast(sonner),Alert.
Not covered — fall back to manual composition
- Marketing-only components (Hero, Pricing, Features) — outside shadcn/ui.
Calendar/DatePicker— composition skill required, not generated.Combobox— built fromCommand+Popover; case-by-case.- Streaming / partial-prerender boundaries — use the project's framework patterns (Next.js / Remix), not shadcn/ui.
Procedure: render a shadcn/ui component for the design brief
Step 0: Inspect
- Read
state.ui_audit.shadcn_inventory.versionand confirm it matches the version in## Compatibilitywithin the same major. If audit flagged a mismatch, the user already chose to proceed — note that instate.changes. - Read
state.ui_audit.design_tokens— every color, spacing, and radius in the rendered output must reference a token from this map. - Read
state.ui_design:components→ the primitive list to compose.microcopy→ button labels, empty-state text, validation messages. Lock — render verbatim.states→ empty / loading / error / success / disabled coverage.a11y→ ARIA labels, keyboard nav, focus order.
Step 1: Compose primitives
- Import primitives from the project's
components/ui/path (@/components/ui/button, …) — never fromshadcnorradix-ui. - Compose Radix-style:
<Dialog>→<DialogTrigger asChild>→<DialogContent>→<DialogHeader>→<DialogTitle>. Never wrapDialogTriggeraround a pre-styled<button>; passasChild. - Use the variant API of
Button(variant="default" | "destructive" | "outline" | "secondary" | "ghost" | "link"); do not override with raw Tailwind for the variant set. - Forms:
useForm(react-hook-form) +zodResolver(schema)→<Form>→<FormField>→<FormItem>→<FormLabel>→<FormControl>→<FormMessage>. Validation messages come from the zod schema, mirrored to the design-brief microcopy.
Step 2: Apply tokens, dark mode, a11y
- Colors via semantic classes:
bg-background,text-foreground,bg-primary text-primary-foreground,text-muted-foreground. Nobg-white/text-black/ hardcoded#fff. - Spacing / radius from theme tokens (
rounded-lgmapped to--radiusintailwind.config.{js,ts}). Polish refactors hardcoded values when a token equivalent exists. - Dark mode: never branch on a
darkprop; rely on the.darkclass on the root and semantic tokens. - Every interactive primitive: keyboard trigger present (Enter/Space
on buttons, Esc on dialogs — Radix free), visible focus ring,
aria-labelfromstate.ui_design.a11ywhen icon-only.
Step 3: State coverage
- Empty: render the design-brief empty-state copy in a
Cardor inline placeholder; nevernull. - Loading:
Skeletonrows for tables;Buttondisabled+Loader2icon for submit-in-flight. - Error:
Alert variant="destructive"with the design-brief message;FormMessagefor field-level errors. - Success:
toast.success(...)fromsonnerwith the design-brief confirmation copy. - Disabled:
disabledprop on the trigger plus the design-brief reason asaria-describedbytext.
Step 4: Validate
- No raw
<input>/<button>/<select>outside the primitive set. - No hardcoded colors / spacing — every value is a token.
- Microcopy matches
state.ui_design.microcopybyte-for-byte. - Dark mode: toggle
.darkon<html>, render the component, every surface still legible (notext-white on bg-white). - Keyboard: Tab through every focusable element; focus ring visible.
Output format
- React component file(s) under the project's
components/(orapp/) tree, importing primitives from@/components/ui/*. - Per file, one entry recorded in
state.changeswithkind="ui",stack="react-shadcn", and the design-brief summary.
Review pass — a11y findings + preview envelope
When this skill is dispatched by directives/ui/review.py (test slot)
or directives/ui/polish.py (verify slot) — i.e. a review/polish run,
not the initial apply — it also emits:
state.ui_review.a11y—{violations: [{rule, selector, severity}, ...], severity_floor?, accepted_violations?}. Run an a11y tool against the rendered output (e.g.axe-corevia Playwright,@axe-core/react,jest-axe) and translate hits into this shape. Use the same(rule, selector)shape asstate.ui_audit.a11y_baselineso the engine's de-dup matches pre-existing entries on replay. Omit the envelope on apply passes; the engine's_apply_a11y_gateonly fires when a baseline is present.state.ui_review.preview—{render_ok: bool, screenshot_path?, dom_dump_path?, error?, skipped?}.render_ok: falsewitherrorpopulated triggers thepreview_render_failedhalt;render_ok: truewithscreenshot_paththreads the screenshot into the delivery report'sartifactslist. Browser tooling (Playwright/Cypress/…) is a consumer-project dependency — this package does not ship one.
Polish dispatch: when the dispatcher skips review because a previous
review pass already returned SUCCESS, this skill MUST itself
synthesise the updated state.ui_review.findings (including any
remaining a11y_violation entries) so the engine's gate sees the
current state on the next polish round.
Do NOT
- Do NOT install
shadcn-uifrom npm — primitives are scaffolded. - Do NOT hardcode colors / spacing / radii — use the token map.
- Do NOT branch on a
darkprop — use semantic tokens + the.darkclass. - Do NOT rewrite microcopy — it is locked by
state.ui_design. - Do NOT skip
asChildonDialogTrigger/SheetTrigger/ similar Radix wrappers — it breaks the accessibility contract. - Do NOT introduce a non-shadcn UI library (MUI, Chakra) into the same surface — pick one system per surface.
Auto-trigger keywords
- shadcn / shadcn ui / shadcn/ui
- React component (when the project uses shadcn)
- Radix primitive
- Tailwind dark mode
- React Hook Form + zod
Source: event4u-app/agent-config — distributed by TomeVault.