Use when reading or writing any shadcn ui component, especially when figuring out which library owns which concern (a11y, variants, class composition, icons), when a prop or class string is not behaving as expected, when picking between raw Radix and the shadcn-wrapped component, when adding tailwind-merge or cva to an existing project, when an LLM is asked to "rebuild a shadcn-style component" and needs to know the exact runtime layers, and when migrating from Tailwind v3 to v4 inside a shadcn project. Prevents the common mistakes of (a) concatenating className strings without `cn()`, causing duplicate utility classes to leak through, (b) bypassing the shadcn-copied component to import Radix primitives directly, losing the project's styling, (c) defining `cva` variants in the wrong order so that `compoundVariants` never trigger, (d) installing wrong package versions (radix-ui unified vs @radix-ui/react-* legacy, cva 1.0 beta vs 0.7 stable, tailwind-merge v2 for Tailwind v3 vs v3 for Tailwind v4), and (e) tre
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Use when reading or writing any shadcn ui component, especially when figuring out which library owns which concern (a11y, variants, class composition, icons), when a prop or class string is not behaving as expected, when picking between raw Radix and the shadcn-wrapped component, when adding tailwind-merge or cva to an existing project, when an LLM is asked to "rebuild a shadcn-style component" and needs to know the exact runtime layers, and when migrating from Tailwind v3 to v4 inside a shadcn project. Prevents the common mistakes of (a) concatenating className strings without `cn()`, causing duplicate utility classes to leak through, (b) bypassing the shadcn-copied component to import Radix primitives directly, losing the project's styling, (c) defining `cva` variants in the wrong order so that `compoundVariants` never trigger, (d) installing wrong package versions (radix-ui unified vs @radix-ui/react-* legacy, cva 1.0 beta vs 0.7 stable, tailwind-merge v2 for Tailwind v3 vs v3 for Tailwind v4), and (e) treating shadcn ui as a runtime dependency when it is a code-generation CLI. Covers the complete runtime stack composition (Radix UI for headless primitives, class-variance-authority for type-safe variants, tailwind-merge for class conflict resolution, clsx for conditional composition, Tailwind CSS v3.4 or v4 for styling, lucide-react for icons), the canonical `cn()` helper, the cva variant signature with TypeScript inference via `VariantProps`, what each layer owns and where it lives in the consumer project, the ownership-model implications (open code, no runtime shadcn import), and the per-component Radix-primitive mapping for the 39 Radix-backed components in the catalog. Keywords: shadcn stack, cva, class-variance-authority, tailwind-merge, twMerge, clsx, cn helper, lib/utils, Radix UI primitives, radix-ui unified package, lucide-react, class composition, why className not applied, duplicate classes Tailwind, variant ordering, compoundVariants not firing, shadcn under the hood, runtime dependencies, ownership model, open code, how does shadcn work, what is cva, how do I merge tailwind classes, shadcn vs radix, why does my override not work, p-3 px-2 conflict.
license
MIT
compatibility
Designed for Claude Code. Requires shadcn ui evergreen-2026.
metadata
{"author":"OpenAEC-Foundation","version":"1.0"}
shadcn ui Core: Stack Composition
shadcn ui is not a runtime component library : it is a CLI that copies TypeScript source files into your project. Once shadcn add button finishes, your project owns components/ui/button.tsx outright, and the only things that remain at runtime are the underlying primitives (Radix, cva, tailwind-merge, clsx, lucide-react, Tailwind). Understanding this stack is the foundation for every other shadcn skill.
Quick Reference
Stack layers (every shadcn project has these)
Layer : owns : package : runtime cost
Headless behaviour + a11y : focus-trap, ARIA, keyboard, RTL, controlled state : radix-ui (unified, Feb 2026) or @radix-ui/react-* (legacy per-component) : small (component-scoped, tree-shaken)
need to ...
├── make a component a11y, focus-trapped, keyboard-navigable, ARIA-compliant?
│ └── ALWAYS use the Radix primitive (via shadcn-wrapped component)
├── add a typed variant prop (`variant="destructive"`, `size="lg"`)?
│ └── ALWAYS use cva + VariantProps ; NEVER inline if/switch on className
├── compose className conditionally (`cn('base', isOpen && 'rotate-180')`)?
│ └── ALWAYS use cn() ; clsx handles the conditional, twMerge resolves conflicts
├── allow caller to override styles (`<Button className="rounded-none" />`)?
│ └── ALWAYS pass caller's className LAST to cn() so twMerge keeps it
├── add an icon?
│ └── ALWAYS use lucide-react (`import { Check } from "lucide-react"`) unless migrated
├── style something visible?
│ └── ALWAYS Tailwind utility classes ; NEVER inline `style={...}` for design tokens
└── render dynamic content with no Radix equivalent (Alert, Card, Badge, Breadcrumb)?
└── pure cva + Tailwind (no Radix), still composed via cn()
Picking between radix-ui unified vs @radix-ui/react-* legacy
new project after Feb 2026?
├── ALWAYS install the unified package : `npm install radix-ui`
└── Import per-primitive : `import { Dialog as DialogPrimitive } from "radix-ui"`
existing project on @radix-ui/react-*?
├── keep `@radix-ui/react-dialog`, `@radix-ui/react-dropdown-menu`, etc.
└── Imports unchanged : `import * as DialogPrimitive from "@radix-ui/react-dialog"`
mixed install (some unified, some per-component)?
└── NEVER mix in the same project ; pick one and migrate fully
Picking tailwind-merge version
project Tailwind version?
├── Tailwind v3.4 (or older) : `tailwind-merge@^2.6.0`
├── Tailwind v4.0 through v4.3 : `tailwind-merge@^3.0.0`
└── mismatch produces silent merge failures on new v4 utilities (e.g. `shadow-xs`)
Picking cva 0.7 stable vs 1.0 beta
production app, want stability?
├── `class-variance-authority@^0.7.1` (stable)
want latest variant features (slot-typed compoundVariants, recipe inheritance)?
├── `class-variance-authority@^1.0.0-beta` (beta as of May 2026)
└── beta API is mostly source-compatible ; review migration notes in cva changelog
Patterns
Pattern 1: The cn() helper
Every shadcn project ships lib/utils.ts with this exact function. NEVER replace it with raw string concatenation.
ALWAYS import the shadcn-wrapped component from @/components/ui/dialog, NOT the Radix primitive directly. Importing Radix bypasses all of the project's styling.
Tree-shaken : only the icons you actually import end up in the bundle. The default size used by shadcn components is size-4 (16px). Animated states use the spinning Loader2 icon with animate-spin.
Reference Links
references/methods.md : complete API signatures (cva, VariantProps, twMerge, clsx, cn, Radix primitive list with import paths).
references/examples.md : working examples (Button with cva, Dialog wrapping Radix, cn merge cases, lucide icon imports).