AgentSkillsCN

Shadcn

Shadcn

SKILL.md

shadcn/ui Component Library for Next.js

shadcn/ui component library for Next.js. Covers installation, component usage, customization, theming with CSS variables, form patterns with react-hook-form and Zod, and accessible component composition. Always check current docs before adding components.

License: MIT Author: Bala Version: 0.1.0

What is shadcn/ui?

shadcn/ui is not a package manager dependency. Instead, it's a collection of component source code that you copy directly into your project. This gives you:

  • Full control over component styling and behavior
  • No dependency bloat
  • Easy to customize without waiting for package updates
  • Built on Radix UI primitives for accessibility
  • Styled with Tailwind CSS by default

Installation

Initialize shadcn/ui

bash
npx shadcn@latest init

This command:

  • Creates components/ui/ directory
  • Sets up Tailwind CSS if not already configured
  • Creates necessary config files

Add Components

bash
# Add individual components
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog

# Add multiple components at once
npx shadcn@latest add button card dialog form input label

Components are copied to components/ui/[component-name].tsx.

Project Structure

code
app/
├── layout.tsx
└── page.tsx
components/
├── ui/
│   ├── button.tsx         # From shadcn
│   ├── card.tsx           # From shadcn
│   ├── dialog.tsx         # From shadcn
│   ├── form.tsx           # From shadcn
│   └── ...                # Other shadcn components
├── MyComponent.tsx        # Your custom components
└── ...

Theming with CSS Variables

shadcn/ui uses CSS custom properties for theming. Customize in app/globals.css:

css
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 0 0% 3.6%;
    --card: 0 0% 100%;
    --card-foreground: 0 0% 3.6%;
    --popover: 0 0% 100%;
    --popover-foreground: 0 0% 3.6%;
    --muted: 0 0% 96.1%;
    --muted-foreground: 0 0% 45.1%;
    --accent: 0 0% 9.1%;
    --accent-foreground: 0 0% 98%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 0 0% 98%;
    --border: 0 0% 89.8%;
    --input: 0 0% 89.8%;
    --ring: 0 0% 3.6%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 0 0% 3.6%;
    --foreground: 0 0% 98%;
    --card: 0 0% 3.6%;
    --card-foreground: 0 0% 98%;
    /* ... other dark mode variables ... */
  }
}

body {
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
}

Dark Mode

Enable in tailwind.config.ts:

typescript
// tailwind.config.ts
export default {
  darkMode: 'class',
  // ...
}

Then toggle the dark class on <html>:

jsx
// components/ThemeToggle.tsx
'use client'

import { useEffect, useState } from 'react'
import { Moon, Sun } from 'lucide-react'
import { Button } from '@/components/ui/button'

export function ThemeToggle() {
  const [isDark, setIsDark] = useState(false)

  useEffect(() => {
    const isDarkMode = document.documentElement.classList.contains('dark')
    setIsDark(isDarkMode)
  }, [])

  const toggleDarkMode = () => {
    document.documentElement.classList.toggle('dark')
    setIsDark(!isDark)
  }

  return (
    <Button variant="ghost" size="icon" onClick={toggleDarkMode}>
      {isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
    </Button>
  )
}

Customization

Modify Component Styling

Components are copied into your project, so you can edit them directly:

typescript
// components/ui/button.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center 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",
  {
    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",
      },
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => (
    <button
      className={cn(buttonVariants({ variant, size, className }))}
      ref={ref}
      {...props}
    />
  )
)

Extend Components

Create wrapper components that extend shadcn components:

typescript
// components/CustomButton.tsx
import { Button } from '@/components/ui/button'

export function CustomButton(props) {
  return (
    <Button 
      className="bg-gradient-to-r from-blue-500 to-purple-600"
      {...props}
    />
  )
}

Form Patterns

Form with Validation (react-hook-form + Zod)

bash
npm install react-hook-form zod @hookform/resolvers
npx shadcn@latest add form input
typescript
// app/page.tsx
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'

const formSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
})

type FormValues = z.infer<typeof formSchema>

export default function LoginForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: '',
      password: '',
    },
  })

  const onSubmit = (data: FormValues) => {
    console.log('Form data:', data)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="you@example.com" {...field} />
              </FormControl>
              <FormDescription>
                We'll never share your email.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit">Sign In</Button>
      </form>
    </Form>
  )
}

Common Component Patterns

Dialog/Modal

bash
npx shadcn@latest add dialog
typescript
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'

export function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure?</DialogTitle>
          <DialogDescription>
            This action cannot be undone.
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  )
}

Sheet (Sidebar/Drawer)

bash
npx shadcn@latest add sheet
typescript
import { Button } from '@/components/ui/button'
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@/components/ui/sheet'

export function SheetDemo() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="outline">Open Menu</Button>
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Menu</SheetTitle>
          <SheetDescription>
            Navigation items here
          </SheetDescription>
        </SheetHeader>
      </SheetContent>
    </Sheet>
  )
}

Toast Notifications

bash
npx shadcn@latest add toast sonner
typescript
'use client'

import { useToast } from '@/components/ui/use-toast'
import { Button } from '@/components/ui/button'

export function ToastDemo() {
  const { toast } = useToast()

  return (
    <Button
      onClick={() => {
        toast({
          title: 'Success',
          description: 'Your action was completed.',
        })
      }}
    >
      Show Toast
    </Button>
  )
}

Data Table

bash
npx shadcn@latest add table
npm install @tanstack/react-table

See component-patterns.md for full data table example.

Accessibility

shadcn/ui is built on Radix UI, which provides excellent accessibility:

  • Semantic HTML
  • ARIA attributes
  • Keyboard navigation
  • Focus management
  • Screen reader support

Always maintain these patterns when customizing components.

Resources

Doc Lookup Strategy

  1. Check llms.txt: https://ui.shadcn.com/llms.txt
  2. Browse component docs: https://ui.shadcn.com/docs/components/[name]
  3. Radix UI docs: For underlying primitives and behavior
  4. Context7 fallback: resolve-library-id("shadcn") → get-library-docs

Always verify the component documentation before adding or customizing components.