
Web Ui Shadcn Ui
- 47 installs
- 19 repo stars
- Updated July 19, 2026
- agents-inc/skills
web-ui-shadcn-ui is a Claude Code skill that teaches an agent shadcn/ui patterns: CLI usage, OKLCH CSS-variable theming, the cn() utility, and component composition.
About
A Claude Code skill that teaches an agent how to work with shadcn/ui, a copy-and-own component system built on Radix primitives and Tailwind. It covers CLI installation, CSS-variable theming in OKLCH, the cn() class-merge utility, compound-component composition, and the Field component for form layout. A developer uses it when adding or customizing shadcn/ui components in a React + Tailwind project.
- shadcn/ui copy-and-own patterns: CLI (npx shadcn@latest add), CSS-variable theming in OKLCH
- Covers the cn() utility, compound-component composition, and the new Field component
- Built on Radix primitives + Tailwind v4
Web Ui Shadcn Ui by the numbers
- 47 all-time installs (skills.sh)
- Ranked #1,322 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
web-ui-shadcn-ui capabilities & compatibility
- Capabilities
- react components · ui theming · component composition · cli scaffolding
- Use cases
- frontend · ui design · web design
- Pricing
- Free
What web-ui-shadcn-ui says it does
shadcn/ui is a copy-and-own component system built on Radix primitives and Tailwind.
You MUST use the CLI to add components - `npx shadcn@latest add [component]` - not manual copy
Theme via CSS custom properties in OKLCH format.
npx skills add https://github.com/agents-inc/skills --skill web-ui-shadcn-uiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 47 |
|---|---|
| repo stars | ★ 19 |
| Last updated | July 19, 2026 |
| Repository | agents-inc/skills ↗ |
What it does
Use when adding, theming, or customizing shadcn/ui components in a React + Tailwind project via the shadcn CLI.
Who is it for?
Adding and customizing shadcn/ui components in a React + Tailwind codebase with full source ownership.
Skip if: Projects not using Tailwind, or teams needing an opinionated closed design system they cannot edit.
When should I use this skill?
Running the shadcn CLI, theming with CSS variables, or composing shadcn components in a React + Tailwind app.
What you get
shadcn/ui components installed via the CLI, themed with OKLCH CSS variables, and merged safely with cn().
- Installed shadcn/ui components
- OKLCH CSS-variable theme
- composed compound components
By the numbers
- Ships 9 bundled example/reference files (command-palette, composition, core, data-table, dialogs, forms, theming, refere
- Uses Radix primitives + Tailwind v4
Files
shadcn/ui Component Patterns
Quick Guide: shadcn/ui is a copy-and-own component system built on Radix primitives and Tailwind. Usenpx shadcn@latest addto install components intocomponents/ui/. Theme via CSS custom properties in OKLCH format. Compose with compound component patterns. Use thecn()utility for class merging. NewFieldcomponent replaces the oldForm/FormFieldpattern for form-library-agnostic field layout.
---
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST use the CLI to add components - `npx shadcn@latest add [component]` - not manual copy)
(You MUST customize components through CSS variables and the cn() utility - not direct style overrides)
(You MUST keep components in the `components/ui/` directory - this is the shadcn convention)
(You MUST define foreground colors for every new background color - `--brand` needs `--brand-foreground`)
</critical_requirements>
---
Auto-detection: shadcn/ui, shadcn, @shadcn, components.json, npx shadcn, cn() utility, ui components, Radix-based components, data-slot, Field component, cva variants
When to use:
- Building React applications with accessible, customizable UI components
- Setting up a copy-and-own component library with full source control
- Implementing CSS variable theming with OKLCH colors and dark mode
- Creating forms with the new Field component (form-library-agnostic)
- Using compound components (Card, Dialog, Sheet, Tabs, Command)
When NOT to use:
- Projects requiring a specific opinionated design system
- Applications where you cannot control the component source
- Projects not using Tailwind CSS
Key patterns covered:
- CLI installation, inspection flags, and component management
- CSS variable theming with OKLCH format and Tailwind v4
- cn() utility for conflict-free class merging
- Compound component composition and extension
- Field component for form-library-agnostic field layout
- Variant system with cva (class-variance-authority)
---
<philosophy>
Philosophy
shadcn/ui is not a traditional component library - it is how you build your component library. Components are copied into your codebase via CLI, giving you full ownership. Core functionality (accessibility, keyboard nav) comes from Radix primitives; the styling layer is yours to customize.
Five Core Principles:
1. Open Code - Component source is visible and modifiable 2. Composition - Consistent, composable compound component interfaces 3. Distribution - CLI and flat-file schema enable component distribution 4. Beautiful Defaults - Carefully curated styling that works out of the box 5. AI-Ready - Open source architecture allows tools to read and improve components
What shadcn/ui handles vs what other skills handle:
- shadcn/ui: component structure, CSS variables, cn() utility, composition patterns, Field layout
- Your styling approach: Tailwind configuration, utility class conventions, custom CSS
- Your form library: useForm hook, validation schemas, submission logic, state management
</philosophy>
---
<patterns>
Core Patterns
Pattern 1: CLI Installation and Management
Always use the CLI to add components. It resolves dependencies, installs Radix packages, and creates proper file structure.
# Initialize (creates components.json)
npx shadcn@latest init
# Add components
npx shadcn@latest add button card dialog
# Inspection flags (CLI v4)
npx shadcn@latest add button --dry-run # Preview changes
npx shadcn@latest add button --diff # Check for updates
npx shadcn@latest add button --view # Display component payload
# Monorepo support
npx shadcn@latest add button --path=packages/ui/src/components
# Project info (useful for AI agents)
npx shadcn@latest info
# View component docs from CLI (v4)
npx shadcn@latest docs comboboxComponents go in components/ui/. The cn() utility goes in lib/utils.ts. Both are created automatically.
Why good: CLI handles dependency resolution, provides inspection before changes, components become owned source code
---
Pattern 2: CSS Variable Theming (OKLCH)
shadcn/ui uses CSS custom properties with OKLCH color format (Tailwind v4). Every color follows a background/foreground naming convention.
/* The naming convention - understand this, don't memorize values */
:root {
--primary: oklch(0.205 0 0); /* Background color */
--primary-foreground: oklch(0.985 0 0); /* Text ON that background */
}
.dark {
--primary: oklch(0.985 0 0); /* Inverted for dark mode */
--primary-foreground: oklch(0.205 0 0);
}Key Tailwind v4 changes from older shadcn versions:
- OKLCH replaces HSL for better perceptual uniformity
- `@theme inline` directive maps CSS variables to Tailwind utilities
- `@custom-variant dark` defines dark mode selector
- *`--chart-
** and **--sidebar-`* variable families for specialized components - Computed radius:
--radius-sm/md/lg/xlderived from base--radius
Adding custom colors requires both the CSS variable AND the @theme inline mapping:
:root {
--brand: oklch(0.627 0.265 303.9);
--brand-foreground: oklch(1 0 0);
}
@theme inline {
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
}Then use as: bg-brand text-brand-foreground hover:bg-brand/90
See examples/theming.md for complete variable reference and custom color examples.
---
Pattern 3: The cn() Utility
Combines clsx (conditional classes) with tailwind-merge (conflict resolution). Consumer className always comes last so overrides work.
// lib/utils.ts - auto-generated by shadcn init
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage in components - className last for consumer overrides
function Card({ className, ...props }: CardProps) {
return (
<div
className={cn("rounded-lg border bg-card shadow-sm", className)}
{...props}
/>
);
}Critical behavior: cn("px-4", "px-8") produces "px-8" (last wins), not "px-4 px-8". This is why consumer className overrides work correctly.
See examples/core.md for more cn() examples.
---
Pattern 4: Component Extension with Variants
Use cva (class-variance-authority) for variant-based component styling. This is how shadcn/ui itself structures Button, Badge, and Alert.
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
lg: "h-11 px-8",
icon: "h-10 w-10",
},
},
defaultVariants: { variant: "default", size: "default" },
},
);To add a new variant, edit the component source directly - you own it. To use variants: <Button variant="destructive" size="sm">.
See examples/composition.md for extended button with loading state and responsive dialog/drawer patterns.
---
Pattern 5: Field Component (Form Layout)
The Field component is the modern form-library-agnostic way to compose form fields. It provides labels, descriptions, error messages, and accessibility - without coupling to any specific form library.
import {
Field,
FieldLabel,
FieldDescription,
FieldError,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
// Field is a layout primitive - bring your own form library
<Field data-invalid={hasError}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" aria-invalid={hasError} {...fieldProps} />
<FieldDescription>We will never share your email.</FieldDescription>
{hasError && <FieldError errors={errors} />}
</Field>;Replaces the old `Form/FormField/FormItem/FormControl/FormMessage` pattern which was tightly coupled to React Hook Form. The Field component works with any form library or server actions.
Related components: FieldGroup for grouping related fields, FieldSet and FieldLegend for semantic grouping.
See examples/forms.md for complete form integration examples.
---
Pattern 6: Compound Component Composition
shadcn/ui uses compound components (Card, Dialog, Sheet, Tabs, Command) with consistent sub-component patterns. Extend by wrapping, not replacing.
// CORRECT: Extend by wrapping compound components
function ProductCard({ title, price, ...props }: ProductCardProps) {
return (
<Card {...props}>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>${price}</CardDescription>
</CardHeader>
</Card>
);
}
// WRONG: Breaking compound structure with plain divs
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
</div>;`asChild` prop - Use when composing interactive elements to avoid nesting (e.g., Button wrapping a Link):
<Button asChild>
<Link href="/dashboard">Dashboard</Link>
</Button>See examples/dialogs.md for AlertDialog, Sheet, and toast patterns. See examples/data-table.md for table with row actions. See examples/command-palette.md for command menu.
---
Pattern 7: New Components (October 2025+)
Recent additions that solve common patterns:
| Component | Purpose | Install |
|---|---|---|
| Field | Form-library-agnostic field layout | npx shadcn@latest add field |
| Spinner | Loading indicator (replaces custom ones) | npx shadcn@latest add spinner |
| Kbd | Keyboard shortcut display | npx shadcn@latest add kbd |
| Button Group | Grouped button container | npx shadcn@latest add button-group |
| Input Group | Input with addons (icons, buttons) | npx shadcn@latest add input-group |
| Item | Flex container for lists/cards | npx shadcn@latest add item |
| Empty | Empty state display | npx shadcn@latest add empty |
These components work across Radix and Base UI primitives.
---
Pattern 8: Recent Platform Changes
Unified Radix UI package (Feb 2026): Individual @radix-ui/react-* packages are now a single radix-ui package. Migrate with npx shadcn@latest migrate radix.
// Old (pre-Feb 2026)
import * as DialogPrimitive from "@radix-ui/react-dialog";
// New (unified package)
import { Dialog as DialogPrimitive } from "radix-ui";CLI v4 (March 2026):
npx shadcn@latest docs [component]- View component docs from CLI (useful for AI agents)npx shadcn@latest init --template- Full project scaffolding for Next.js, Vite, Astro, React Router, TanStack Start, Laravel--presetflag packs entire design system config (colors, fonts, radius, icons) into a shareable codeshadcn/skills- AI agent context for component patterns and registry workflowsregistry:base- Distribute entire design systems as single payloads
RTL support (Jan 2026): First-class right-to-left layout support. The CLI transforms physical CSS classes to logical equivalents at install time.
</patterns>
---
Detailed Resources:
- examples/core.md - Setup, cn() utility, skeleton loading
- examples/composition.md - Extended button, responsive dialog/drawer
- examples/forms.md - Field component, form integration patterns
- examples/dialogs.md - AlertDialog, Sheet, toast patterns, dark mode provider
- examples/data-table.md - Sortable table with row actions
- examples/command-palette.md - Command menu with keyboard navigation
- examples/theming.md - Complete CSS variables, custom colors, theme toggle
- reference.md - Decision frameworks, anti-patterns, checklists
---
<red_flags>
RED FLAGS
High Priority Issues:
- Not using CLI for installation - Manual copy misses dependencies and proper file structure
- Breaking OKLCH format - Wrapping values in
hsl()when they are already OKLCH - Not using cn() - Direct className concatenation breaks Tailwind class merging
- Missing components.json - CLI commands will fail without configuration
- Hardcoding colors - Use CSS variables for theme consistency
Medium Priority Issues:
- Overriding styles with !important - Use cn() and proper class ordering instead
- Not exposing className prop - Custom components should accept className for override
- Ignoring accessibility attributes - Radix provides them; custom extensions can break them
- Not updating both :root and .dark - New colors need both light and dark mode
Gotchas & Edge Cases:
- Foreground convention -
--primary-foregroundis text color ON--primarybackground, not primary-colored text - React 19 - No
forwardRefneeded;refis now a regular prop. Components usedata-slotattributes - `asChild` required for Link composition - Prevents nested interactive elements (button inside anchor)
- Select controlled usage - Requires both
onValueChangeanddefaultValue(orvalue) - Chart config (Tailwind v4) - Use
var(--chart-1)directly, nohsl()wrapper needed - `suppressHydrationWarning` - Required on
<html>when using theme provider to prevent hydration mismatch - Old Form components -
Form/FormField/FormItem/FormControl/FormMessageare legacy; preferFieldcomponent for new code - Base UI option - Since Feb 2026, you can choose between Radix and Base UI as the primitive library (
--base radixor--base base) - Unified Radix package - Since Feb 2026, import from
radix-ui(not individual@radix-ui/react-*packages). Migrate withnpx shadcn@latest migrate radix
</red_flags>
---
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use the CLI to add components - `npx shadcn@latest add [component]` - not manual copy)
(You MUST customize components through CSS variables and the cn() utility - not direct style overrides)
(You MUST keep components in the `components/ui/` directory - this is the shadcn convention)
(You MUST define foreground colors for every new background color - `--brand` needs `--brand-foreground`)
Failure to follow these rules will break component updates, cause styling conflicts, and violate shadcn/ui conventions.
</critical_reminders>
shadcn/ui - Command Palette Examples
Command menu with keyboard navigation and search. See core.md for setup basics.
---
Command Menu with Keyboard Navigation
"use client";
import { useEffect, useState, useCallback } from "react";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
interface CommandMenuProps {
onNavigate: (path: string) => void;
}
export function CommandMenu({ onNavigate }: CommandMenuProps) {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
const runCommand = useCallback((command: () => void) => {
setOpen(false);
command();
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Navigation">
<CommandItem
onSelect={() => runCommand(() => onNavigate("/calendar"))}
>
<span>Calendar</span>
</CommandItem>
<CommandItem onSelect={() => runCommand(() => onNavigate("/search"))}>
<span>Search</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem
onSelect={() => runCommand(() => onNavigate("/profile"))}
>
<span>Profile</span>
<CommandShortcut>Ctrl+P</CommandShortcut>
</CommandItem>
<CommandItem
onSelect={() => runCommand(() => onNavigate("/settings"))}
>
<span>Settings</span>
<CommandShortcut>Ctrl+S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}Why good: Cmd/Ctrl+K keyboard shortcut, search filters items automatically, shortcuts displayed for discoverability, closes after action, navigation callback is framework-agnostic
shadcn/ui - Composition Examples
Extending components and responsive patterns.
---
Extended Button with Loading State
Adding isLoading Prop to Button Component (React 19)
// components/ui/button.tsx (extended for React 19)
import * as React from "react";
import { Slot } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
isLoading?: boolean;
}
// React 19: No forwardRef needed - ref is a regular prop
function Button({
className,
variant,
size,
asChild = false,
isLoading,
children,
disabled,
ref,
...props
}: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={disabled || isLoading}
data-slot="button"
{...props}
>
{isLoading && <Spinner />}
{children}
</Comp>
);
}
export { Button, buttonVariants };Usage
<Button isLoading>Saving...</Button>
<Button isLoading={isSubmitting}>Submit</Button>Why good: Loading state built into component, disabled while loading prevents double-submission, Spinner component (npx shadcn@latest add spinner) provides visual feedback
---
Responsive Dialog/Drawer
Dialog on Desktop, Drawer on Mobile
"use client";
import { useState } from "react";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
export function ResponsiveDialog({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
if (isDesktop) {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here.
</DialogDescription>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
}
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="text-left">
<DrawerTitle>Edit profile</DrawerTitle>
<DrawerDescription>
Make changes to your profile here.
</DrawerDescription>
</DrawerHeader>
<div className="px-4">{children}</div>
<DrawerFooter className="pt-2">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}useMediaQuery Hook
// hooks/use-media-query.ts
import { useEffect, useState } from "react";
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addEventListener("change", listener);
return () => media.removeEventListener("change", listener);
}, [matches, query]);
return matches;
}Why good: Dialog on desktop for quick interaction, Drawer on mobile for touch-friendly UI, same content rendered in both, hook encapsulates media query logic
shadcn/ui - Core Examples
Essential setup and utility patterns for shadcn/ui projects.
---
Project Setup
Initialization
# Initialize a new shadcn/ui project
npx shadcn@latest init
# When prompted, select:
# - Style: New York (recommended; "default" style is deprecated)
# - Base color: Slate, Gray, Zinc, Neutral, Stone, Mauve, Olive, Mist, Taupe
# - CSS variables: Yes
# - Tailwind config: (leave blank for Tailwind v4)
# With options (CLI v4)
npx shadcn@latest init --base radix # Specify primitive library
npx shadcn@latest init --preset a1Dg5eFl # Use shared design system preset
npx shadcn@latest init --monorepo # Monorepo setupcomponents.json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}Why good: Explicit aliases for consistent imports, CSS variables enable theming, iconLibrary configures default icon set
---
cn() Utility Examples
Conditional Classes
import { cn } from "@/lib/utils";
// Conditional classes
<div className={cn("base-class", isActive && "active-class")} />
// Multiple conditions
<div
className={cn(
"px-4 py-2 rounded-md",
isDisabled && "opacity-50 cursor-not-allowed",
isLoading && "animate-pulse",
variant === "primary" && "bg-primary text-primary-foreground",
variant === "secondary" && "bg-secondary text-secondary-foreground"
)}
/>
// Component with consumer className override
function Card({ className, ...props }: CardProps) {
return (
<div
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className // Consumer's classes always last - overrides work
)}
{...props}
/>
);
}Tailwind Merge Behavior
// Without tailwind-merge (broken)
clsx("px-4", "px-6"); // "px-4 px-6" - both applied, unpredictable
// With cn() (correct)
cn("px-4", "px-6"); // "px-6" - later class wins
// Real example: consumer override works correctly
<Button className="px-8">Wide Button</Button>;
// Internal "px-4" replaced by "px-8", not both appliedWhy good: cn() resolves Tailwind class conflicts intelligently, consumer overrides work as expected
---
Skeleton Loading
Card and List Skeletons
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
export function CardSkeleton() {
return (
<Card>
<CardHeader className="gap-2">
<Skeleton className="h-5 w-1/5" />
<Skeleton className="h-4 w-4/5" />
</CardHeader>
<CardContent className="h-10" />
</Card>
);
}
export function UserListSkeleton() {
return (
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center space-x-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
))}
</div>
);
}Why good: Skeleton matches actual content layout dimensions, provides smooth loading transition
shadcn/ui - Data Table Examples
Sortable tables with row actions and dropdown menus.
---
Sortable Table with Row Actions
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// Use your configured icon library (lucide-react is the shadcn default)
interface User {
id: string;
name: string;
email: string;
role: string;
createdAt: Date;
}
interface UserTableProps {
users: User[];
onEdit: (user: User) => void;
onDelete: (user: User) => void;
}
export function UserTable({ users, onEdit, onDelete }: UserTableProps) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[200px]">
<Button variant="ghost" className="p-0 hover:bg-transparent">
Name
{/* Sort icon from your icon library */}
<span className="ml-2 h-4 w-4" aria-hidden="true">
↕
</span>
</Button>
</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<span className="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-primary/10 text-primary">
{user.role}
</span>
</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
{/* More icon from your icon library */}
<span className="h-4 w-4" aria-hidden="true">
...
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onEdit(user)}>
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onDelete(user)}
className="text-destructive"
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}Why good: Action menu keeps table clean, destructive action has visual warning, sortable headers are discoverable, sr-only text for accessibility
shadcn/ui - Dialog Examples
Patterns for dialogs, sheets, and toast notifications. See core.md for setup basics.
---
Confirmation Dialog (AlertDialog)
Use AlertDialog for destructive actions that need explicit confirmation. It blocks interaction until the user responds.
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
interface ConfirmDeleteProps {
onConfirm: () => void;
itemName: string;
}
export function ConfirmDelete({ onConfirm, itemName }: ConfirmDeleteProps) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete{" "}
<span className="font-medium">{itemName}</span>.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={onConfirm}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}Why good: AlertDialog blocks interaction for destructive actions, destructive styling on confirm button reinforces severity, asChild on trigger prevents nested buttons
---
Sheet (Side Panel)
Use Sheet for contextual editing without leaving the current page (settings, user details, filters).
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
SheetFooter,
SheetClose,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function EditUserSheet({ user }: { user: User }) {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Edit User</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit User</SheetTitle>
<SheetDescription>Make changes to the user profile.</SheetDescription>
</SheetHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" defaultValue={user.name} className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right">
Email
</Label>
<Input
id="email"
defaultValue={user.email}
className="col-span-3"
/>
</div>
</div>
<SheetFooter>
<SheetClose asChild>
<Button type="submit">Save changes</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}Why good: Sheet keeps context visible, consistent header/footer pattern, SheetClose auto-closes on action
---
Toast Examples (Sonner)
Toasts provide non-blocking feedback. shadcn/ui uses Sonner for its toast system.
Setup
// Add sonner: npx shadcn@latest add sonner
// In your layout - add Toaster component once
import { Toaster } from "@/components/ui/sonner";
export function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
);
}Usage Patterns
import { toast } from "sonner";
// Success
toast.success("Profile updated", {
description: "Your changes have been saved.",
});
// Error
toast.error("Something went wrong", {
description: "Please try again later.",
});
// Toast with undo action
toast("Event created", {
description: "Your event has been scheduled.",
action: {
label: "Undo",
onClick: () => handleUndo(),
},
});
// Promise toast (loading → success/error)
toast.promise(saveSettings(), {
loading: "Saving...",
success: "Settings saved!",
error: "Could not save settings.",
});Why good: Promise toast handles loading/success/error in one call, action button enables undo patterns, non-blocking feedback
shadcn/ui - Form Examples
Field component patterns for form-library-agnostic field layout. See core.md for setup basics.
---
Field Component Basics
The Field component provides accessible form field layout (labels, descriptions, errors) without coupling to any specific form library.
Sub-components: Field, FieldContent, FieldLabel, FieldDescription, FieldError, FieldTitle, FieldGroup, FieldSet, FieldLegend, FieldSeparator
Orientation prop: "vertical" (default), "horizontal", "responsive" (auto-switches via container queries)
import {
Field,
FieldLabel,
FieldDescription,
FieldError,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
// Basic field - works with any form library or server actions
<Field>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" />
<FieldDescription>Your public display name.</FieldDescription>
</Field>
// Field with error state
<Field data-invalid={!!error}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" aria-invalid={!!error} />
{error && <FieldError errors={[error]} />}
</Field>
// Horizontal field (label and control side-by-side)
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="theme">Theme</FieldLabel>
<FieldDescription>Select your preferred theme.</FieldDescription>
</FieldContent>
<Select id="theme" />
</Field>Why good: Form-library-agnostic, consistent accessibility attributes, replaces the old tightly-coupled Form/FormField/FormItem pattern, FieldError supports Standard Schema validators (Zod, Valibot, ArkType) directly
---
Field with Form Library Integration
The Field component works with any form library via its Controller or equivalent. The key pattern: pass data-invalid to Field, aria-invalid to the control, and render FieldError conditionally.
// Generic pattern - adapt to your form library's controller
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input {...field} id={field.name} aria-invalid={fieldState.invalid} />
<FieldDescription>We will never share your email.</FieldDescription>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>---
Field with Select
<Controller
name="role"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Role</FieldLabel>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger id={field.name} aria-invalid={fieldState.invalid}>
<SelectValue placeholder="Select a role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="guest">Guest</SelectItem>
</SelectContent>
</Select>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>Why good: Select binds via value + onValueChange (not spread), Field handles error display
---
FieldGroup and FieldSet
Group related fields for visual and semantic coherence.
import { FieldSet, FieldLegend, FieldGroup } from "@/components/ui/field";
// FieldSet for semantic grouping (renders <fieldset>)
<FieldSet>
<FieldLegend>Contact Information</FieldLegend>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" />
</Field>
<Field>
<FieldLabel htmlFor="phone">Phone</FieldLabel>
<Input id="phone" type="tel" />
</Field>
</FieldGroup>
</FieldSet>
// FieldGroup for visual grouping without semantics
<FieldGroup orientation="horizontal">
<Field>
<FieldLabel htmlFor="first">First Name</FieldLabel>
<Input id="first" />
</Field>
<Field>
<FieldLabel htmlFor="last">Last Name</FieldLabel>
<Input id="last" />
</Field>
</FieldGroup>Why good: Semantic HTML via fieldset/legend for accessibility, responsive orientation support, no coupling to form state
---
Legacy Form Pattern (Pre-October 2025)
Note: The Form/FormField/FormItem/FormControl/FormMessage components are still available but are tightly coupled to React Hook Form. Prefer the Field component for new code.// Legacy pattern - still works but coupled to specific form library
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your email address.</FormDescription>
<FormMessage />
</FormItem>
)}
/>Why legacy: Tightly coupled to one form library, harder to switch form solutions, Field component provides same layout with any form library
shadcn/ui - Theming Examples
Custom colors, dark mode setup, and theme-aware components. See core.md for CSS structure basics.
Prerequisites: Understand the CSS variable naming convention (background/foreground pairs, OKLCH format) from SKILL.md Pattern 2.
---
Custom Brand Colors (OKLCH)
Adding colors beyond the defaults requires three things: CSS variables in :root and .dark, and the @theme inline mapping.
:root {
/* Custom brand colors - add alongside default shadcn variables */
--brand: oklch(0.627 0.265 303.9); /* Purple */
--brand-foreground: oklch(1 0 0);
--success: oklch(0.527 0.154 150.069); /* Green */
--success-foreground: oklch(1 0 0);
--warning: oklch(0.795 0.184 86.047); /* Amber */
--warning-foreground: oklch(0.145 0 0);
}
.dark {
--brand: oklch(0.627 0.265 303.9);
--brand-foreground: oklch(1 0 0);
--success: oklch(0.627 0.194 149.214);
--success-foreground: oklch(1 0 0);
--warning: oklch(0.795 0.184 86.047);
--warning-foreground: oklch(0.145 0 0);
}
/* Map to Tailwind utilities */
@theme inline {
/* ...existing mappings... */
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}Usage
<Button className="bg-brand text-brand-foreground hover:bg-brand/90">
Brand Button
</Button>
<Badge className="bg-success text-success-foreground">
Success
</Badge>Why good: Custom colors follow same convention as defaults, opacity modifiers work (/90), dark mode automatic via .dark overrides
---
Dark Mode Setup
Theme Provider
// providers.tsx
"use client";
// shadcn/ui docs recommend next-themes for Next.js projects
// For other frameworks, see shadcn dark mode docs: ui.shadcn.com/docs/dark-mode
import { ThemeProvider } from "next-themes";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
);
}
// layout.tsx - suppressHydrationWarning prevents flash
import { Providers } from "./providers";
export function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Theme Toggle with Dropdown
"use client";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ThemeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
{/* Sun/Moon icons - use your icon solution */}
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Why good: Dropdown provides all theme options, system detection works automatically, suppressHydrationWarning prevents flash
---
Theme-Aware Custom Components
Using CSS Variables for Status Colors
import { cn } from "@/lib/utils";
export function StatusCard({
status,
}: {
status: "success" | "warning" | "error";
}) {
const statusColors = {
success: "bg-success/10 text-success border-success/20",
warning: "bg-warning/10 text-warning border-warning/20",
error: "bg-destructive/10 text-destructive border-destructive/20",
};
return (
<div className={cn("rounded-lg border p-4", statusColors[status])}>
{/* Content automatically adapts to light/dark theme */}
</div>
);
}Reading Theme Colors in JavaScript
Only needed for third-party libraries (charts, canvas) that require color values programmatically.
import { useTheme } from "next-themes";
export function ChartWrapper() {
const { resolvedTheme } = useTheme();
const getColor = (variable: string) => {
if (typeof window === "undefined") return "";
return getComputedStyle(document.documentElement)
.getPropertyValue(variable)
.trim();
};
// OKLCH values are already complete - no wrapper needed
const primaryColor = getColor("--primary");
return <ThirdPartyChart color={primaryColor} />;
}Why good: Components adapt to theme automatically, JavaScript access only when truly needed, OKLCH values used directly
---
Chart Configuration (Tailwind v4)
With Tailwind v4, theme colors include the color format. Remove any hsl() wrapper from older configs.
// Tailwind v4 - OKLCH values include format, no wrapper needed
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig;Why good: Direct variable reference, no format wrapper confusion between HSL and OKLCH
# yaml-language-server: $schema=https://raw.githubusercontent.com/agents-inc/cli/main/src/schemas/metadata.schema.json
category: web-ui-components
slug: shadcn-ui
domain: web
author: "@vince"
displayName: shadcn/ui
cliDescription: Tailwind component library
usageGuidance: Use when using shadcn/ui components, theming, or customization.
shadcn/ui Reference
Decision frameworks, anti-patterns, and red flags for shadcn/ui development. See SKILL.md for core concepts and examples/ for code examples.
---
Decision Framework
When to Use shadcn/ui vs Other Options
Need UI components?
├─ Do you want full control over component source?
│ ├─ YES → shadcn/ui is ideal
│ └─ NO → Consider a traditional component library
├─ Are you using Tailwind CSS?
│ ├─ YES → shadcn/ui integrates perfectly
│ └─ NO → Consider other options or add Tailwind
├─ Do you need accessible components?
│ └─ YES → shadcn/ui (built on Radix/Base UI) provides this
└─ Do you need a specific design system (Material, etc.)?
├─ YES → Use that design system's library
└─ NO → shadcn/ui works with any designComponent Addition Decision
Need a new component?
├─ Is it in shadcn/ui registry?
│ ├─ YES → npx shadcn@latest add [component]
│ └─ NO → Build custom component following shadcn patterns
├─ Does component need customization?
│ ├─ Styling only → Use CSS variables or cn()
│ ├─ Behavior change → Modify the component source
│ └─ Major change → Create wrapper or new component
└─ Is it a one-off component?
├─ YES → Build without variant system
└─ NO → Follow shadcn variant patterns (cva)Theming Decision
Need to change appearance?
├─ Is it a global color change?
│ └─ Modify CSS variables in globals.css + @theme inline mapping
├─ Is it a component-specific style?
│ ├─ All instances → Modify component source
│ └─ One instance → Use className prop with cn()
├─ Is it dark mode?
│ └─ Update variables in .dark class
└─ Is it a new color?
└─ Add CSS variable + foreground pair + @theme inline mappingDialog vs Sheet vs Drawer
Need to display content in an overlay?
├─ Is it a confirmation or alert?
│ └─ AlertDialog (blocks interaction until response)
├─ Is it a form or detailed content?
│ ├─ On desktop → Dialog (centered modal)
│ └─ On mobile → Drawer (slides from bottom)
├─ Is it contextual editing (list item, settings)?
│ └─ Sheet (slides from side)
├─ Does it need to stay open while interacting with page?
│ └─ Sheet (side panel pattern)
└─ Is it a quick action or selection?
└─ Popover or DropdownMenuForm Component Selection
Building a form field?
├─ Is it text input?
│ ├─ Single line → Input
│ ├─ Multi-line → Textarea
│ └─ Sensitive → Input type="password"
├─ Is it a selection?
│ ├─ Few options (2-5) → RadioGroup or Tabs
│ ├─ Many options → Select or Combobox
│ └─ Multiple selections → Checkbox group
├─ Is it a boolean?
│ ├─ On/off setting → Switch
│ └─ Agreement/Terms → Checkbox
├─ Is it a date/time?
│ └─ Calendar or DatePicker
└─ Wrapping any field?
└─ Use Field component (not legacy FormField)---
RED FLAGS
See SKILL.md <red_flags> section for the complete list of red flags, gotchas, and edge cases.
---
Anti-Patterns
Direct Style Overrides
Use CSS variables and cn() instead of inline styles or style prop overrides.
// WRONG - Inline styles break theming
<Button style={{ backgroundColor: "#3b82f6" }}>Click me</Button>
// WRONG - Hardcoded Tailwind classes bypass theme
<Button className="bg-blue-500 hover:bg-blue-600">Click me</Button>
// CORRECT - Use variant system
<Button variant="default">Click me</Button>
// CORRECT - Use CSS variable-based classes via cn()
<Button className={cn("bg-brand hover:bg-brand/90")}>Click me</Button>Manual Component Copy
Use the CLI instead of manually copying component code.
# WRONG - Manual copy from documentation
# Copy-pasting code from ui.shadcn.com
# CORRECT - Use CLI (resolves deps, installs Radix packages, creates utils)
npx shadcn@latest add button
npx shadcn@latest add card dialog formIgnoring the Variant System
Use the variant system for component variations instead of conditional classes.
// WRONG - Ad-hoc conditional classes
<button
className={`px-4 py-2 ${
isPrimary ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-800"
}`}
>
Click
</button>
// CORRECT - Use variant props
<Button variant={isPrimary ? "default" : "secondary"}>Click</Button>
// CORRECT - Add new variant to component source if needed
const buttonVariants = cva("...", {
variants: {
variant: {
// ...existing variants
brand: "bg-brand text-brand-foreground hover:bg-brand/90",
},
},
});Breaking Composition Patterns
Maintain compound component patterns when customizing.
// WRONG - Breaking compound structure
<div className="card">
<div className="card-header"><h2>{title}</h2></div>
</div>
// CORRECT - Use compound components
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
</Card>Missing Foreground Colors
Always define foreground colors when adding new background colors.
/* WRONG - Background without foreground */
:root {
--brand: oklch(0.627 0.265 303.9);
/* Missing --brand-foreground! */
}
/* CORRECT - Pair background with foreground, both modes */
:root {
--brand: oklch(0.627 0.265 303.9);
--brand-foreground: oklch(1 0 0);
}
.dark {
--brand: oklch(0.627 0.265 303.9);
--brand-foreground: oklch(1 0 0);
}Not Using asChild for Polymorphism
Use asChild prop to compose with other components like Link.
// WRONG - Nested interactive elements
<Button>
<Link href="/dashboard">Dashboard</Link>
</Button>
// CORRECT - asChild merges components
<Button asChild>
<Link href="/dashboard">Dashboard</Link>
</Button>---
Quick Reference
CLI Commands
npx shadcn@latest init # Initialize project
npx shadcn@latest init --base radix # Specify primitive library
npx shadcn@latest init --preset CODE # Use design system preset
npx shadcn@latest init --template # Scaffold full project
npx shadcn@latest add [component] # Add component(s)
npx shadcn@latest add button --dry-run # Preview changes
npx shadcn@latest add button --diff # Check for updates
npx shadcn@latest add button --view # Inspect payload
npx shadcn@latest info # Show project context
npx shadcn@latest docs combobox # View component docs
npx shadcn@latest migrate radix # Migrate to unified radix-ui packageEssential CSS Variables (Tailwind v4 OKLCH)
| Variable | Purpose |
|---|---|
--background / --foreground | Page background / text |
--primary / --primary-foreground | Primary action colors |
--secondary / --secondary-foreground | Secondary action colors |
--muted / --muted-foreground | Subtle backgrounds / text |
--destructive / --destructive-foreground | Danger/error colors |
--accent / --accent-foreground | Accent backgrounds / text |
--border | Border color |
--input | Input border color |
--ring | Focus ring color |
--radius | Border radius base |
--chart-1 through --chart-5 | Chart color palette |
--sidebar / --sidebar-* | Sidebar component colors |
Component Checklist
- [ ] Used CLI to add component (
npx shadcn@latest add) - [ ] Component is in
components/ui/directory - [ ] Using
cn()for class merging - [ ] CSS variables used for colors (not hardcoded)
- [ ] Foreground color defined for new backgrounds
- [ ] Both
:rootand.darkupdated for new colors - [ ] Using variant system for component variations
- [ ]
asChildused when composing with Link - [ ] Accessibility attributes preserved
- [ ]
classNameprop exposed on custom components
---
Sources
Related skills
FAQ
How do you add shadcn/ui components?
Use the CLI: `npx shadcn@latest add [component]`, which installs into components/ui/.
What color format does shadcn/ui use?
CSS custom properties in OKLCH format with Tailwind v4.