shadcn
Building UI with shadcn/ui components for consistent, accessible interfaces. Use when creating forms, buttons, dialogs, tables, and other UI elements with React and Tailwind CSS.
When & Why to Use This Skill
This Claude skill empowers developers to build high-quality, accessible user interfaces using shadcn/ui, React, and Tailwind CSS. It streamlines the process of integrating, customizing, and theming UI components like forms, dialogs, and tables while ensuring adherence to best practices for accessibility, maintainability, and modern frontend architecture.
Use Cases
- Rapid UI Prototyping: Quickly generate and integrate consistent, accessible components like buttons, cards, and inputs into React applications using shadcn/ui patterns.
- Advanced Form Development: Build complex, validated forms by combining shadcn/ui components with React Hook Form and Zod for robust data handling and error state management.
- Custom Design Systems: Implement brand-specific theming and dark mode support using CSS variables, OKLCH colors, and Tailwind CSS configuration for a unique visual identity.
- Accessibility Compliance: Ensure web interfaces meet WCAG standards through built-in ARIA attributes, semantic HTML, and managed keyboard navigation patterns.
- Component Customization: Create reusable component variants and compound components using class-variance-authority (cva) to maintain a clean and scalable codebase.
| name | shadcn |
|---|---|
| description | Building UI with shadcn/ui components for consistent, accessible interfaces. Use when creating forms, buttons, dialogs, tables, and other UI elements with React and Tailwind CSS. |
| license | MIT |
| author | devrecruit |
| version | "1.0" |
| compatibility | React 16.8+, Tailwind CSS 3+ |
Shadcn Skills
This skill covers best practices for integrating and customizing shadcn/ui components to build accessible, maintainable interfaces.
Component Integration
Purpose
Seamlessly integrate shadcn/ui components into your application with proper setup and usage patterns.
Initial Setup
Install shadcn/ui
# Initialize shadcn/ui in your project npx shadcn-ui@latest init # Or for an existing project npx shadcn-ui@latest add buttonComponents Directory Structure
components/ ├── ui/ │ ├── button.tsx │ ├── card.tsx │ ├── field.tsx │ ├── input.tsx │ └── ... └── [feature]/ └── [component].tsxUsing Components
import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; export function MyComponent() { return ( <Card> <Input placeholder="Enter text..." /> <Button>Submit</Button> </Card> ); }
Common Components
Field Components with Base UI
import { Field } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; export function MyForm() { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(schema), defaultValues: { /* ... */ }, }); // Call server action on submit (preferred over API route) const onSubmit = async (data) => { const result = await createEntityAction(data); if (result.success) { // Handle success } }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Field label="Email" error={errors.email?.message}> <Input placeholder="user@example.com" {...register("email")} /> </Field> <Button type="submit">Submit</Button> </form> ); }Dialog/Modal Components
import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; export function MyDialog() { const [open, setOpen] = React.useState(false); return ( <Dialog open={open} onOpenChange={setOpen}> <DialogContent> <DialogHeader> <DialogTitle>Dialog Title</DialogTitle> </DialogHeader> {/* Content */} </DialogContent> </Dialog> ); }Table Component
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; export function DataTable({ data, columns }) { return ( <Table> <TableHeader> <TableRow> {columns.map((col) => ( <TableHead key={col.key}>{col.label}</TableHead> ))} </TableRow> </TableHeader> <TableBody> {data.map((row) => ( <TableRow key={row.id}> {columns.map((col) => ( <TableCell key={col.key}>{row[col.key]}</TableCell> ))} </TableRow> ))} </TableBody> </Table> ); }
Theming
Purpose
Customize shadcn/ui components to match your application's brand and style.
Theming Strategy
CSS Variables
- shadcn uses CSS variables for theming
- Configure colors in
app/globals.css - Support light and dark modes
Color Configuration
/* app/globals.css */ @layer base { :root { --background: 0 0% 100%; --foreground: 0 0% 3.6%; --primary: 0 72.2% 50.6%; --secondary: 0 0% 96.1%; /* ... more colors ... */ } .dark { --background: 0 0% 3.6%; --foreground: 0 0% 98%; /* ... dark mode colors ... */ } }OKLCH Colors (Tailwind v4)
- Use OKLCH format for better color spaces
- Example:
oklch(50% 0.1 240) - More perceptually uniform than RGB
Custom Component Variants
// components/ui/button.tsx import { cva, type VariantProps } from "class-variance-authority"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3 text-xs", lg: "h-11 px-8", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {} export function Button({ className, variant, size, ...props }: ButtonProps) { return ( <button className={buttonVariants({ variant, size, className })} {...props} /> ); }Dark Mode Setup
// components/theme-provider.tsx "use client"; import { ThemeProvider as NextThemesProvider } from "next-themes"; export function ThemeProvider({ children, ...props }) { return ( <NextThemesProvider attribute="class" defaultTheme="system" enableSystem {...props} > {children} </NextThemesProvider> ); }
Accessibility
Purpose
Ensure shadcn/ui components are accessible to all users, including those using assistive technologies.
Key Principles
Semantic HTML
- shadcn components use semantic elements
- Buttons are buttons, not divs
- Links are links, not buttons
- Forms use proper label associations
ARIA Attributes
- shadcn includes proper ARIA labels
- Use
aria-labelfor icon-only buttons - Use
aria-describedbyfor form hints
Keyboard Navigation
- All interactive elements are keyboard accessible
- Tab order is logical and visible
- Escape key closes modals and dropdowns
- Enter key submits forms
Color Contrast
- Ensure sufficient contrast ratios (WCAG AA minimum)
- Don't rely on color alone to convey information
- Test with accessibility tools
Accessible Forms
const { register, formState: { errors }, } = useForm({ resolver: zodResolver(schema), }); <Field label="Email Address" error={errors.email?.message}> <Input id="email" type="email" aria-invalid={!!errors.email} aria-describedby={errors.email ? "email-error" : undefined} placeholder="user@example.com" {...register("email")} /> {errors.email && ( <p id="email-error" className="text-sm text-destructive"> {errors.email.message} </p> )} </Field>;Focus Management
- Visible focus indicators (built into shadcn)
- Modal focus trapping (Dialog component)
- Focus restoration after closing modals
Testing for Accessibility
- Use accessibility tools (axe DevTools, WAVE)
- Test keyboard navigation manually
- Test with screen readers (NVDA, JAWS, VoiceOver)
- Check color contrast ratios
- Verify form labels and error messages
Advanced Patterns
Custom Hooks with shadcn
// hooks/use-form-state.ts
import { useCallback } from "react";
import { useForm } from "react-hook-form";
export function useFormState(schema, onSubmit) {
const form = useForm({
resolver: zodResolver(schema),
});
const handleSubmit = useCallback(
async (data) => {
try {
await onSubmit(data);
} catch (error) {
form.setError("root", { message: error.message });
}
},
[form, onSubmit]
);
return { ...form, handleSubmit };
}
Compound Components
// components/card-with-form.tsx
export function CardWithForm({ title, onSubmit, children }) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={onSubmit}>{children}</form>
</CardContent>
</Card>
);
}
Development Checklist
When using shadcn/ui:
- Components are properly imported from @/components/ui
- Color theme is configured in globals.css
- Dark mode is implemented with ThemeProvider
- Forms use react-hook-form + Zod validation
- Forms call server actions instead of API routes
- Using Base UI components instead of Radix UI
- Using Field components instead of Form/FormField
- All interactive elements have proper ARIA labels
- Keyboard navigation is tested
- Color contrast meets WCAG AA standards
- Focus indicators are visible
- Components follow project naming conventions
- Custom variants are documented