shadcn/ui logo

shadcn/ui

Beautiful accessible React components library

$ npx docs2skills add shadcn-ui
SKILL.md

shadcn/ui

Beautiful accessible React components with full source code control

What this skill does

shadcn/ui is a component system that gives you the actual component source code instead of a packaged library. You copy beautifully-designed, accessible components directly into your project where you can fully customize them. Built on Radix UI primitives with Tailwind CSS styling.

Unlike traditional component libraries where you're stuck with predefined APIs and limited customization, shadcn/ui hands you the code. Need to modify a Button's behavior? Edit the component file directly. Want to add new variants? Change the code. This "open code" approach eliminates the common problems of style overrides, component wrapping, and API limitations.

The system uses a CLI tool and registry schema to distribute components across projects, ensuring consistency while maintaining full control. Every component follows composable patterns, making them predictable for both developers and AI tools.

Prerequisites

  • React 18+ with TypeScript
  • Tailwind CSS configured
  • Node.js 18+ for CLI tool
  • Framework: Next.js, Vite, Remix, or other React framework

Quick start

# Initialize shadcn/ui in your project
npx shadcn@latest init

# Add a component
npx shadcn@latest add button

# Use the component
import { Button } from "@/components/ui/button"

export function MyComponent() {
  return (
    <Button variant="destructive" size="lg">
      Delete Account
    </Button>
  )
}

The init command creates components.json config and sets up your project structure. Components are installed to components/ui/ by default.

Core concepts

Open Code: Components are copied to your project as editable source files, not imported from node_modules.

Composition: All components use consistent, composable interfaces built on Radix UI primitives with class-variance-authority for variant management.

Registry System: Components are distributed via a schema-based registry that defines dependencies, files, and installation metadata.

Design Tokens: Uses CSS variables for theming, allowing easy customization of colors, spacing, and typography across all components.

Accessibility First: Built on Radix UI primitives ensuring ARIA compliance, keyboard navigation, and screen reader support.

Key component patterns

Button with variants:

import { Button } from "@/components/ui/button"

<Button variant="default">Primary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline" size="sm">Small Outline</Button>
<Button variant="ghost" size="icon">
  <Icon className="h-4 w-4" />
</Button>

Form components:

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Button } from "@/components/ui/button"

<div className="grid w-full max-w-sm items-center gap-1.5">
  <Label htmlFor="email">Email</Label>
  <Input type="email" id="email" placeholder="Email" />
  <Button type="submit">Submit</Button>
</div>

Dialog composition:

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Confirm Action</DialogTitle>
    </DialogHeader>
    <p>Are you sure you want to continue?</p>
  </DialogContent>
</Dialog>

Data table with sorting:

import { DataTable } from "@/components/ui/data-table"
import { ColumnDef } from "@tanstack/react-table"

const columns: ColumnDef<User>[] = [
  {
    accessorKey: "email",
    header: "Email",
  },
  {
    accessorKey: "name",
    header: "Name",
  },
]

<DataTable columns={columns} data={users} />

CLI commands

# Initialize project
npx shadcn@latest init

# Add specific components
npx shadcn@latest add button input label

# Add all components
npx shadcn@latest add

# List available components
npx shadcn@latest list

# Update components
npx shadcn@latest update button

# Diff components (see changes)
npx shadcn@latest diff button

Configuration

components.json:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

Theme customization in CSS:

:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  --destructive: 0 84.2% 60.2%;
  --border: 214.3 31.8% 91.4%;
  --radius: 0.5rem;
}

Component customization

Modifying variants (edit components/ui/button.tsx):

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        // Add custom variant
        success: "bg-green-600 text-white hover:bg-green-700",
      },
    }
  }
)

Adding new props:

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
  loading?: boolean // Add custom prop
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, loading, ...props }, ref) => {
    // Handle loading state
    if (loading) {
      return <Spinner />
    }
    // ... rest of component
  }
)

Best practices

  • Always use asChild prop when composing with other components to avoid wrapper divs
  • Leverage CSS variables for theme consistency instead of hardcoding colors
  • Use the cn() utility function to merge classes properly with conflict resolution
  • Follow the established file structure: components in components/ui/, utilities in lib/
  • Prefer composition over props - build complex components by combining simpler ones
  • Use TypeScript interfaces that extend HTML element props for better DX
  • Implement proper forwardRef for all interactive components
  • Test accessibility with keyboard navigation and screen readers

Gotchas and common mistakes

CSS variable scope: Theme variables must be defined at :root level. Scoped CSS variables won't work across components.

Tailwind prefix conflicts: If using Tailwind prefix in config, update component classes manually - CLI doesn't auto-convert prefixed classes.

AsChild prop confusion: asChild uses Radix's Slot component. The child must be a single element and will receive all props from the parent.

Import path issues: Components use absolute imports via TypeScript paths. Ensure baseUrl and paths are configured in tsconfig.json.

Radix peer dependencies: Components depend on specific Radix packages. Missing peer deps cause runtime errors - install all listed dependencies.

CSS cascade order: Component styles can be overridden by global CSS loaded later. Use !important sparingly or adjust CSS load order.

Form integration: Form components don't include validation by default. Use with react-hook-form or similar for form state management.

Bundle size: Adding components copies all dependencies. Use tree-shaking and only add components you need.

Version conflicts: Mixing shadcn/ui versions can cause style inconsistencies. Update all components together using CLI.

Custom registries: When using custom registries, ensure schema matches official format or components won't install correctly.