npx skills add ...
npx skills add vercel/vercel-plugin --skill shadcn
npx skills add vercel/vercel-plugin --skill shadcn
shadcn/ui expert guidance — CLI, component installation, composition patterns, custom registries, theming, Tailwind CSS integration, and high-quality interface design. Use when initializing shadcn, adding components, composing product UI, building custom registries, configuring themes, or troubleshooting component issues.
The same skill content is published under more than one repo. The install counts are split across them; any of these commands works.
You are an expert in shadcn/ui — a collection of beautifully designed, accessible, and customizable React components built on Radix UI primitives and Tailwind CSS. Components are added directly to your codebase as source code, not installed as a dependency.
shadcn/ui is not a component library in the traditional sense. You don't install it as a package. Instead, the CLI copies component source code into your project, giving you full ownership and customization ability.
IMPORTANT: shadcn init is interactive by default. Always use -d (defaults) for non-interactive initialization:
AI Elements compatibility: Always use
--base radix(the default) when the project uses or may use AI Elements. AI Elements components rely on Radix APIs and have type errors with Base UI.
Options:
-d, --defaults — Use default configuration, skip all interactive prompts (REQUIRED for CI/agent use)-y, --yes — Skip confirmation prompts (does NOT skip library selection — use -d instead)-f, --force — Force overwrite existing configuration-t, --template — Scaffold full project template (next, vite, react-router, astro, laravel, tanstack-start)--preset — Apply a design system preset (colors, theme, icons, fonts, radius) as a single shareable code--base — Choose primitive library: radix (default) or base-ui--monorepo — Set up a monorepo structureWARNING:
-y/--yesalone does NOT make init fully non-interactive — it still prompts for component library selection. Always use-dto skip ALL prompts.
Deprecated in CLI v4:
--style,--base-color,--src-dir,--no-base-style, and--css-variablesflags are removed and will error. Theregistry:buildandregistry:mcpregistry types are also deprecated. Useregistry:baseandregistry:fontinstead.
The init command:
components.json configurationcn() utility functionOptions:
-o, --overwrite — Overwrite existing files-p, --path — Custom install path-a, --all — Install all components--dry-run — Preview what will be added without writing files--diff — Show diff of changes when updating existing components--view — Display a registry item's source code inline
shadcn docsgives coding agents the context to use primitives correctly — returns code examples, API reference, and usage patterns inline.
shadcn/skills gives coding agents the context they need to work with components and registries correctly. It covers both Radix and Base UI primitives, updated APIs, component patterns, and registry workflows. The skill knows how to use the CLI, when to invoke it, and which flags to pass — so agents produce code that matches your design system.
Install: pnpm dlx skills add shadcn/ui
The new-york style now uses a single radix-ui package instead of individual @radix-ui/react-* packages:
To migrate existing projects: npx shadcn@latest migrate radix. After migration, remove unused @radix-ui/react-* packages from package.json.
shadcn/ui now supports Base UI as an alternative to Radix UI for the underlying primitive library. Components look and behave the same way regardless of which library you choose — only the underlying implementation changes.
Choose during init: npx shadcn@latest init --base base-ui
The CLI pulls the correct component variant based on your project configuration automatically.
The components.json file configures how shadcn/ui works in your project:
Configure multiple registries for your project:
Install using namespace syntax:
shadcn/ui uses CSS custom properties for theming, defined in globals.css:
For dark mode, use the dark class on <html>:
Or use next-themes for toggling:
Add application-specific colors alongside shadcn defaults:
Use in components:
| Component | Use Case |
|---|---|
button | Actions, form submission |
card | Content containers |
dialog | Modals, confirmation prompts |
input / textarea | Form fields |
select | Dropdowns |
table | Data display |
tabs | View switching |
command | Command palette (Cmd+K) |
dropdown-menu | Context menus |
popover | Floating content |
tooltip | Hover hints |
badge | Status indicators |
avatar | User profile images |
scroll-area | Scrollable containers |
separator | Visual dividers |
label | Form labels |
sheet | Slide-out panels |
skeleton | Loading placeholders |
shadcn/ui is not only a component source generator. In the Vercel stack it is the default interface language. Do not stop at "the component works." Compose pages that feel deliberate, high-signal, and consistent.
new-york for product, dashboard, AI, and admin surfaces.--color-primary.bg-background, bg-card, text-foreground, text-muted-foreground, border-border, ring-ring. Avoid ad-hoc hex values.--radius: 0.625rem is a strong baseline.gap-6 / p-6 / text-sm) or compact (gap-4 / p-4 / text-sm).h-4 w-4 or h-5 w-5.| Use case | Reach for this first | Why |
|---|---|---|
| Settings page | Tabs + Card + Form | Clear information grouping with predictable save flows |
| Data dashboard | Card + Badge + Table + DropdownMenu | Covers summary, status, dense data, and row actions without custom shells |
| CRUD table | Table + DropdownMenu + Sheet + AlertDialog | Supports browse, act, edit, and destructive confirmation in a standard pattern |
| Auth screen | Card + Label + Input + Button + Alert | Keeps entry flows focused and gives errors a proper treatment |
| Global search | Command + Dialog | Fast keyboard-first discovery with an established interaction model |
| Mobile nav | Sheet + Button + Separator | Provides a compact navigation shell that adapts cleanly to small screens |
| Detail page | header + Badge + Separator + Card | Balances hierarchy, metadata, and supporting content without over-nesting |
| Filters | Card sidebar + Sheet + Select | Works for persistent desktop filters and collapsible mobile controls |
| Empty/loading/error states | Card + Skeleton + Alert | Gives non-happy paths a designed surface instead of placeholder text |
Tabs + Card per group + Separator + save actionCards + filter bar + TableBadge + main Card + side Card + AlertDialog for destructiveCommand for quick find, Popover for pickers, Sheet for mobile filtersCard + social Separator + inline Alert for errorsAlertDialog (not Dialog) for confirmationbutton / input / select / div when shadcn primitives existdiv rounded-xl border p-6 instead of Tabs / Table / Sheet / DialogDialog for destructive confirmation instead of AlertDialogCreate your own component registry to share across projects:
| Type | Purpose |
|---|---|
registry:ui | Individual UI components |
registry:base | Full design system payload — components, deps, CSS vars, fonts, config |
registry:font | Font configuration as a first-class registry item |
shadcn init Breaks Geist Font in Next.js (Tailwind v4)shadcn init rewrites globals.css and may introduce --font-sans: var(--font-sans) — a circular self-reference that breaks font loading. Tailwind v4's @theme inline resolves CSS custom properties at parse time, not runtime — so even var(--font-geist-sans) won't work because Next.js injects that variable via className at runtime.
The fix: Use literal font family names in @theme inline:
After running shadcn init, always:
@theme inline with literal Geist font names (as shown above)<body> to <html> in layout.tsx:size PropThe shadcn Avatar component does not accept a size variant prop. Control size with Tailwind classes:
This applies to most shadcn components — they use Tailwind classes for sizing, not variant props. If you need reusable size variants, add them yourself via cva in the component source.
All shadcn components use the cn() utility for conditional class merging:
Since you own the source code, extend components directly:
Many components require TooltipProvider at the root:
Presets bundle your entire design system config (colors, theme, icon library, fonts, radius) into a single shareable code. One string configures everything:
Build custom presets on shadcn/create — preview how colors, fonts, and radius apply to real components before publishing.
The CLI handles RTL transformation at install time:
Converts directional classes (ml-4, left-2) to logical properties (ms-4, start-2) automatically.
npx shadcn@latest init --template next -d
npx shadcn@latest init --template vite -d# Add specific components
npx shadcn@latest add button dialog card
# Add all available components
npx shadcn@latest add --all
# Add from a custom registry
npx shadcn@latest add @v0/dashboard
npx shadcn@latest add @acme/custom-button
# Add from AI Elements registry
npx shadcn@latest add https://elements.ai-sdk.dev/api/registry/all.jsonnpx shadcn@latest search button
npx shadcn@latest list @v0npx shadcn@latest build
npx shadcn@latest build ./registry.json -o ./public/r# View a registry item's source before installing
npx shadcn@latest view button
# Show project diagnostics — config, installed components, dependencies
npx shadcn@latest info
# Get docs, code, and examples for any component (agent-friendly output)
npx shadcn@latest docs button
npx shadcn@latest docs dialognpx shadcn@latest migrate rtl # RTL support migration
npx shadcn@latest migrate radix # Migrate to unified radix-ui package
npx shadcn@latest migrate icons # Icon library changes
# Migrate components outside the default ui directory
npx shadcn@latest migrate radix src/components/custom// OLD — individual packages
import * as DialogPrimitive from "@radix-ui/react-dialog"
// NEW — unified package
import { Dialog as DialogPrimitive } from "radix-ui"{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "zinc", // Options: gray, neutral, slate, stone, zinc, mauve, olive, mist, taupe
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"v0": {
"url": "https://v0.dev/chat/api/registry"
},
"ai-elements": {
"url": "https://elements.ai-sdk.dev/api/registry"
}
}
}{
"registries": {
"acme": {
"url": "https://acme.com/registry/{name}.json"
},
"private": {
"url": "https://internal.company.com/registry/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}npx shadcn@latest add @acme/header @private/auth-form@theme inline {
--color-background: oklch(0.145 0 0);
--color-foreground: oklch(0.985 0 0);
--color-card: oklch(0.205 0 0);
--color-card-foreground: oklch(0.985 0 0);
--color-primary: oklch(0.488 0.243 264.376);
--color-primary-foreground: oklch(0.985 0 0);
--color-secondary: oklch(0.269 0 0);
--color-secondary-foreground: oklch(0.985 0 0);
--color-muted: oklch(0.269 0 0);
--color-muted-foreground: oklch(0.708 0 0);
--color-accent: oklch(0.269 0 0);
--color-accent-foreground: oklch(0.985 0 0);
--color-destructive: oklch(0.396 0.141 25.723);
--color-border: oklch(0.269 0 0);
--color-input: oklch(0.269 0 0);
--color-ring: oklch(0.488 0.243 264.376);
--radius: 0.625rem;
/* CLI v4: radius tokens use multiplicative calc instead of additive */
--radius-xs: calc(var(--radius) * 0.5);
--radius-sm: calc(var(--radius) * 0.75);
--radius-md: calc(var(--radius) * 0.875);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.5);
}// app/layout.tsx
<html lang="en" className="dark">import { ThemeProvider } from 'next-themes'
<ThemeProvider attribute="class" defaultTheme="dark">
{children}
</ThemeProvider>@theme inline {
/* shadcn defaults above... */
/* Custom app colors */
--color-priority-urgent: oklch(0.637 0.237 15.163);
--color-priority-high: oklch(0.705 0.213 47.604);
--color-status-done: oklch(0.723 0.219 149.579);
}<span className="text-[var(--color-priority-urgent)]">Urgent</span>
// Or with Tailwind v4 theme():
<span className="text-priority-urgent">Urgent</span>[
{
"name": "my-component",
"type": "registry:ui",
"title": "My Component",
"description": "A custom component",
"files": [
{
"path": "components/my-component.tsx",
"type": "registry:ui"
}
],
"dependencies": ["lucide-react"]
}
]npx shadcn@latest build
# Outputs to public/r/my-component.jsonnpx shadcn@latest add https://your-domain.com/r/my-component.json/* In @theme inline — CORRECT (literal names) */
--font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
--font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, monospace;
/* WRONG — circular, resolves to nothing */
--font-sans: var(--font-sans);
/* ALSO WRONG — @theme inline can't resolve runtime CSS variables */
--font-sans: var(--font-geist-sans);// layout.tsx — font variables on <html>, not <body>
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
<body className="antialiased">// WRONG — no size variant exists
<Avatar size="lg" /> // ❌ TypeScript error / silently ignored
// CORRECT — use Tailwind
<Avatar className="h-12 w-12">
<AvatarImage src={user.image} />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
// Small avatar
<Avatar className="h-6 w-6"> ... </Avatar>import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}// components/ui/button.tsx — add your custom variant
const buttonVariants = cva('...', {
variants: {
variant: {
default: '...',
destructive: '...',
// Add custom variants
success: 'bg-green-600 text-white hover:bg-green-700',
premium: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
},
},
})// app/layout.tsx
import { TooltipProvider } from '@/components/ui/tooltip'
export default function RootLayout({ children }) {
return (
<html lang="en" className="dark">
<body>
<TooltipProvider>{children}</TooltipProvider>
</body>
</html>
)
}# Apply a preset during init
npx shadcn@latest init --preset <code>
# Switch presets in an existing project (reconfigures everything including components)
npx shadcn@latest init --preset <code>npx shadcn@latest migrate rtl