
Shadcn
- 70 installs
- 101 repo stars
- Updated November 28, 2025
- blencorp/claude-code-kit
shadcn/ui is a Claude Code skill that provides shadcn/ui component patterns built on Radix UI primitives and Tailwind CSS.
About
shadcn/ui is a skill that gives Claude patterns for building UI with shadcn/ui components, Radix UI primitives, and Tailwind CSS. A developer uses it when creating tables, forms, dialogs, cards, or buttons, installing shadcn components, or styling with shadcn patterns. It emphasizes copying components into the codebase rather than installing them as dependencies.
- shadcn/ui component patterns built on Radix UI primitives and Tailwind CSS
- Copy-don't-install model with components living in your codebase
- Form patterns with react-hook-form and zod validation
Shadcn by the numbers
- 70 all-time installs (skills.sh)
- Ranked #1,157 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
shadcn capabilities & compatibility
- Capabilities
- ui design · frontend · component styling
- Use cases
- frontend · ui design
- IDEs
- vscode · cursor ide
What shadcn says it does
shadcn/ui component library patterns with Radix UI primitives and Tailwind CSS.
**Copy, Don't Install**: Components are copied to your project, not installed as dependencies
**Accessible**: Built on Radix UI primitives with ARIA support
npx skills add https://github.com/blencorp/claude-code-kit --skill shadcnAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 70 |
|---|---|
| repo stars | ★ 101 |
| Last updated | November 28, 2025 |
| Repository | blencorp/claude-code-kit ↗ |
What it does
Build accessible React UI with shadcn/ui components, Radix primitives, and Tailwind.
Who is it for?
Building accessible React UI with shadcn/ui and Tailwind
Skip if: Non-shadcn component libraries or backend work
When should I use this skill?
Creating UI components with shadcn/ui or installing shadcn components
What you get
UI is built from copied, customizable shadcn components with accessible Radix primitives.
- shadcn components
- Forms
- Dialogs
By the numbers
- 4 documented CLI-failure troubleshooting solutions
Files
shadcn/ui Development Guidelines
Best practices for using shadcn/ui components with Tailwind CSS and Radix UI primitives.
Core Principles
1. Copy, Don't Install: Components are copied to your project, not installed as dependencies 2. Customizable: Modify components directly in your codebase 3. Accessible: Built on Radix UI primitives with ARIA support 4. Type-Safe: Full TypeScript support 5. Composable: Build complex UIs from simple primitives
Installation
Initial Setup
npx shadcn@latest initAdd Components
# Add individual components
npx shadcn@latest add button
npx shadcn@latest add form
npx shadcn@latest add dialog
# Add multiple
npx shadcn@latest add button card dialogTroubleshooting
npm Cache Errors (ENOTEMPTY)
If npx shadcn@latest add fails with npm cache errors like ENOTEMPTY or syscall rename:
Solution 1: Clear npm cache
npm cache clean --force
npx shadcn@latest add tableSolution 2: Use pnpm (recommended)
pnpm dlx shadcn@latest add tableSolution 3: Use yarn
yarn dlx shadcn@latest add tableSolution 4: Manual component installation
Visit the shadcn/ui documentation for the specific component and copy the code directly into your project.
Component Usage
Button & Card
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
// Variants
<Button>Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
// Card
<Card>
<CardHeader>
<CardTitle>{post.title}</CardTitle>
<CardDescription>{post.author}</CardDescription>
</CardHeader>
<CardContent>
<p>{post.excerpt}</p>
</CardContent>
</Card>Dialog
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
export function CreatePostDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>Create Post</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Post</DialogTitle>
<DialogDescription>
Fill in the details below to create a new post.
</DialogDescription>
</DialogHeader>
<PostForm />
</DialogContent>
</Dialog>
);
}Forms
Basic Form with react-hook-form
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
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';
import { Textarea } from '@/components/ui/textarea';
const formSchema = z.object({
title: z.string().min(1, 'Title is required'),
content: z.string().min(10, 'Content must be at least 10 characters')
});
export function PostForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
title: '',
content: ''
}
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
console.log(values);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Post title" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Content</FormLabel>
<FormControl>
<Textarea placeholder="Write your post..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Create Post</Button>
</form>
</Form>
);
}Select Field
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="tech">Technology</SelectItem>
<SelectItem value="design">Design</SelectItem>
<SelectItem value="business">Business</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>Data Display
Table
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
export function PostsTable({ posts }: { posts: Post[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Title</TableHead>
<TableHead>Author</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{posts.map((post) => (
<TableRow key={post.id}>
<TableCell className="font-medium">{post.title}</TableCell>
<TableCell>{post.author.name}</TableCell>
<TableCell>
<Badge variant={post.published ? 'default' : 'secondary'}>
{post.published ? 'Published' : 'Draft'}
</Badge>
</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm">Edit</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}Navigation
Badge & Dropdown Menu
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
export function UserMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<User className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Log out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Tabs
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
export function PostTabs() {
return (
<Tabs defaultValue="published">
<TabsList>
<TabsTrigger value="published">Published</TabsTrigger>
<TabsTrigger value="drafts">Drafts</TabsTrigger>
<TabsTrigger value="archived">Archived</TabsTrigger>
</TabsList>
<TabsContent value="published">
<PublishedPosts />
</TabsContent>
<TabsContent value="drafts">
<DraftPosts />
</TabsContent>
<TabsContent value="archived">
<ArchivedPosts />
</TabsContent>
</Tabs>
);
}Feedback
Toast
'use client';
import { useToast } from '@/components/ui/use-toast';
import { Button } from '@/components/ui/button';
export function ToastExample() {
const { toast } = useToast();
return (
<Button
onClick={() => {
toast({
title: 'Post created',
description: 'Your post has been published successfully.'
});
}}
>
Create Post
</Button>
);
}
// With variant
toast({
variant: 'destructive',
title: 'Error',
description: 'Failed to create post. Please try again.'
});Alert
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AlertCircle } from 'lucide-react';
export function AlertExample() {
return (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
);
}Loading States
Skeleton
import { Skeleton } from '@/components/ui/skeleton';
export function PostCardSkeleton() {
return (
<div className="flex flex-col space-y-3">
<Skeleton className="h-[125px] w-full rounded-xl" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
);
}Customization
Modifying Components
Components are in your codebase - edit them directly:
// components/ui/button.tsx
export const buttonVariants = cva(
"inline-flex items-center justify-center...",
{
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
brand: "bg-gradient-to-r from-blue-500 to-purple-600 text-white"
}
}
}
);Using Custom Variant
<Button variant="brand">Custom Brand Button</Button>Theming
CSS Variables (OKLCH Format)
shadcn/ui now uses OKLCH color format for better color accuracy and perceptual uniformity:
/* app/globals.css */
@layer base {
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
/* ... */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.598 0.15 264);
--primary-foreground: oklch(0.205 0 0);
/* ... */
}
}Dark Mode
// components/theme-toggle.tsx
'use client';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
export function ThemeToggle() {
const { setTheme, theme } = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
);
}Composition Patterns
Combining Components
export function CreatePostCard() {
return (
<Card>
<CardHeader>
<CardTitle>Create Post</CardTitle>
<CardDescription>Share your thoughts with the world</CardDescription>
</CardHeader>
<CardContent>
<PostForm />
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Save Draft</Button>
<Button>Publish</Button>
</CardFooter>
</Card>
);
}Modal with Form
export function CreatePostModal() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>New Post</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Create Post</DialogTitle>
</DialogHeader>
<PostForm onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>
);
}Additional Resources
For detailed information, see:
- Component Catalog
- Form Patterns
- Theming Guide
Component Catalog
Comprehensive guide to all shadcn/ui components.
Layout Components
Card
- Basic card structure
- Card with header, content, footer
- Clickable cards
- Card grid layouts
Separator
- Horizontal and vertical separators
- Use in layouts and forms
Aspect Ratio
- Responsive image containers
- Video embeds
Form Components
Input
- Text, email, password inputs
- Input with icons
- Input validation states
Textarea
- Multi-line text input
- Resizable options
Select
- Single select dropdown
- Multi-select
- Searchable select
Checkbox & Radio
- Single checkbox
- Checkbox groups
- Radio groups
Switch
- Toggle switch
- With labels
Slider
- Range slider
- Multiple handles
Date Picker
- Single date
- Date range
- Time picker
Data Display
Table
- Basic table
- Sortable columns
- Pagination
- Row selection
Badge
- Status badges
- Count badges
- Custom colors
Avatar
- User avatars
- With fallback
- Avatar groups
Progress
- Linear progress
- Circular progress
- With labels
Feedback
Toast
- Success, error, warning toasts
- Action buttons
- Auto-dismiss
Alert
- Info, warning, error alerts
- Dismissible alerts
- With actions
Alert Dialog
- Confirmation dialogs
- Destructive actions
Overlay
Dialog (Modal)
- Basic modal
- Form in modal
- Nested modals
- Scrollable content
Drawer
- Sheet from side
- With form
Popover
- Contextual info
- Form popover
Tooltip
- Simple tooltip
- Rich content tooltip
Navigation
Dropdown Menu
- Basic menu
- With icons
- With keyboard shortcuts
- Nested menus
Command
- Command palette
- Searchable commands
- With keyboard navigation
Menubar
- Application menu
- With submenus
Navigation Menu
- Horizontal navigation
- With dropdowns
Tabs
- Basic tabs
- Vertical tabs
- With icons
Breadcrumb
- Page hierarchy
- With dropdown
Utility
Accordion
- Single or multiple open
- With icons
Collapsible
- Expandable sections
Context Menu
- Right-click menu
Hover Card
- Preview on hover
Scroll Area
- Custom scrollbars
For implementation examples, see the main SKILL.md file.
Form Patterns
Advanced form patterns with react-hook-form and shadcn/ui.
Form Validation
Zod Schema
import * as z from 'zod';
const postSchema = z.object({
title: z.string().min(1, 'Title is required').max(100),
content: z.string().min(10, 'Content must be at least 10 characters'),
category: z.string(),
tags: z.array(z.string()).min(1, 'At least one tag is required'),
published: z.boolean(),
publishedAt: z.date().optional()
});Complex Forms
const form = useForm<z.infer<typeof postSchema>>({
resolver: zodResolver(postSchema),
defaultValues: {
title: '',
content: '',
category: '',
tags: [],
published: false
}
});Field Arrays
import { useFieldArray } from 'react-hook-form';
const { fields, append, remove } = useFieldArray({
control: form.control,
name: 'tags'
});
// Add field
<Button type="button" onClick={() => append({ value: '' })}>
Add Tag
</Button>
// Render fields
{fields.map((field, index) => (
<div key={field.id}>
<Input {...form.register(`tags.${index}.value`)} />
<Button onClick={() => remove(index)}>Remove</Button>
</div>
))}Async Validation
const schema = z.object({
username: z.string().refine(
async (username) => {
const available = await checkUsernameAvailable(username);
return available;
},
{ message: 'Username is already taken' }
)
});Multi-Step Forms
const [step, setStep] = useState(0);
const steps = [
{ title: 'Basic Info', fields: ['title', 'category'] },
{ title: 'Content', fields: ['content', 'tags'] },
{ title: 'Publish', fields: ['published', 'publishedAt'] }
];
const validateStep = async () => {
const fields = steps[step].fields as (keyof typeof form.formState.errors)[];
const valid = await form.trigger(fields);
return valid;
};
const nextStep = async () => {
const valid = await validateStep();
if (valid) setStep(step + 1);
};Dependent Fields
const category = form.watch('category');
<FormField
control={form.control}
name="subcategory"
render={({ field }) => (
<Select {...field} disabled={!category}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{getSubcategories(category).map(sub => (
<SelectItem key={sub} value={sub}>{sub}</SelectItem>
))}
</SelectContent>
</Select>
)}
/>Form State
const {
formState: { errors, isSubmitting, isDirty, isValid }
} = form;
<Button
type="submit"
disabled={isSubmitting || !isDirty || !isValid}
>
{isSubmitting ? 'Saving...' : 'Save Post'}
</Button>Error Handling
const onSubmit = async (values: z.infer<typeof postSchema>) => {
try {
await createPost(values);
toast({ title: 'Success', description: 'Post created' });
} catch (error) {
form.setError('root', {
message: 'Failed to create post. Please try again.'
});
}
};
// Display root error
{form.formState.errors.root && (
<Alert variant="destructive">
<AlertDescription>
{form.formState.errors.root.message}
</AlertDescription>
</Alert>
)}Reset and Defaults
// Reset to defaults
form.reset();
// Reset to specific values
form.reset({
title: post.title,
content: post.content
});
// Reset single field
form.resetField('title');Theming Guide
Comprehensive theming and customization for shadcn/ui using OKLCH color format.
CSS Variables (OKLCH Format)
shadcn/ui now uses OKLCH (Oklch color space) for CSS variables, providing better color accuracy and perceptual uniformity compared to HSL.
Why OKLCH?
- Perceptually uniform: Changes in lightness correspond to predictable changes in perceived brightness
- Better color accuracy: More precise color representation
- Wider gamut: Access to more vivid colors
- Standard in 2025: Adopted with Tailwind v4 and modern browsers
Complete Variable Set
@layer base {
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.961 0 0);
--secondary-foreground: oklch(0.145 0 0);
--muted: oklch(0.961 0 0);
--muted-foreground: oklch(0.469 0 0);
--accent: oklch(0.961 0 0);
--accent-foreground: oklch(0.145 0 0);
--destructive: oklch(0.602 0.22 29);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.914 0 0);
--input: oklch(0.914 0 0);
--ring: oklch(0.205 0 0);
--radius: 0.5rem;
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.598 0.15 264);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.175 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.175 0 0);
--muted-foreground: oklch(0.651 0 0);
--accent: oklch(0.175 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.306 0.15 29);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.175 0 0);
--input: oklch(0.175 0 0);
--ring: oklch(0.48 0.18 264);
}
}Understanding OKLCH Syntax
--color: oklch(L C H);- L (Lightness): 0 to 1 (0 = black, 1 = white)
- C (Chroma): 0 to ~0.4 (0 = gray, higher = more vivid)
- H (Hue): 0 to 360 degrees (color wheel angle)
Examples:
--white: oklch(1 0 0); /* Pure white */
--black: oklch(0 0 0); /* Pure black */
--blue: oklch(0.598 0.15 264); /* Vivid blue */
--red: oklch(0.602 0.22 29); /* Vivid red */Dark Mode Implementation
With next-themes
npm install next-themes// app/providers.tsx
'use client';
import { ThemeProvider } from 'next-themes';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
);
}// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Theme Toggle Component
'use client';
import { Moon, Sun } from 'lucide-react';
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="ghost" size="icon">
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<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>
);
}Custom Colors
Adding Brand Colors (OKLCH)
:root {
--brand: oklch(0.5 0.2 200); /* Brand blue */
--brand-foreground: oklch(1 0 0); /* White text */
}
.dark {
--brand: oklch(0.4 0.18 200); /* Darker brand blue */
--brand-foreground: oklch(1 0 0); /* White text */
}Using Custom Colors
// Add to button variants
brand: "bg-brand text-brand-foreground hover:bg-brand/90"Color Palette Examples
/* Blue palette */
--blue-50: oklch(0.97 0.01 240);
--blue-500: oklch(0.55 0.22 260);
--blue-900: oklch(0.25 0.15 270);
/* Green palette */
--green-50: oklch(0.97 0.02 140);
--green-500: oklch(0.60 0.20 145);
--green-900: oklch(0.30 0.15 150);
/* Red palette */
--red-50: oklch(0.97 0.02 25);
--red-500: oklch(0.60 0.22 29);
--red-900: oklch(0.30 0.15 30);Border Radius
Adjust global border radius:
:root {
--radius: 0.5rem; /* Default */
}
/* More rounded */
:root {
--radius: 0.75rem;
}
/* Less rounded */
:root {
--radius: 0.25rem;
}
/* Square */
:root {
--radius: 0;
}Component-Specific Customization
Modifying Button Component
// components/ui/button.tsx
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",
// Add custom variant with OKLCH
gradient: "bg-gradient-to-r from-blue-500 to-purple-600 text-white hover:from-blue-600 hover:to-purple-700",
brand: "bg-brand text-brand-foreground hover:bg-brand/90",
},
size: {
default: "h-10 px-4 py-2",
// Add custom size
xs: "h-7 px-2 text-xs",
}
}
}
);Typography
Font Configuration
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
const robotoMono = Roboto_Mono({ subsets: ['latin'], variable: '--font-mono' });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}/* globals.css */
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-family: var(--font-sans), sans-serif;
}
code {
font-family: var(--font-mono), monospace;
}
}Multiple Themes (OKLCH)
/* Blue theme */
.theme-blue {
--primary: oklch(0.533 0.22 260);
--primary-foreground: oklch(0.985 0 0);
--accent: oklch(0.7 0.15 250);
/* ... */
}
/* Green theme */
.theme-green {
--primary: oklch(0.45 0.20 145);
--primary-foreground: oklch(0.985 0 0);
--accent: oklch(0.7 0.15 140);
/* ... */
}
/* Purple theme */
.theme-purple {
--primary: oklch(0.58 0.22 300);
--primary-foreground: oklch(0.985 0 0);
--accent: oklch(0.7 0.15 290);
/* ... */
}<body className={theme}>
{children}
</body>Migrating from HSL to OKLCH
If you have existing HSL values, convert them to OKLCH:
Online Tools
- Use https://oklch.com/ for HSL → OKLCH conversion
- Tailwind CSS v4 color tools
Common Conversions
/* HSL → OKLCH */
hsl(221, 83%, 53%) → oklch(0.598 0.15 264) /* Blue */
hsl(0, 84%, 60%) → oklch(0.602 0.22 29) /* Red */
hsl(142, 71%, 45%) → oklch(0.60 0.20 145) /* Green */Best Practices
1. Use OKLCH for new projects: Better color accuracy and perceptual uniformity 2. Maintain contrast ratios: Ensure accessibility (WCAG AA minimum 4.5:1) 3. Test in dark mode: Verify all colors work in both themes 4. Consistent chroma values: Use similar chroma for harmonious palettes 5. Document custom colors: Add comments explaining brand color choices 6. Use CSS variables: Leverage shadcn's variable system for consistency
Browser Support
OKLCH is supported in:
- Chrome 111+
- Safari 15.4+
- Firefox 113+
- Edge 111+
For older browsers, Tailwind v4 provides automatic fallbacks.
{
"shadcn": {
"type": "domain",
"enforcement": "suggest",
"priority": "high",
"promptTriggers": {
"keywords": [
"shadcn",
"shadcn/ui",
"npx shadcn",
"@/components/ui",
"table component",
"form component",
"dialog component",
"card component",
"button component"
],
"intentPatterns": [
"create.*(table|form|dialog|card|button)",
"add.*(table|form|dialog|card|button).*component",
"add.*shadcn.*component",
"install.*shadcn.*component",
"style.*component.*shadcn",
"use.*shadcn"
]
},
"fileTriggers": {
"pathPatterns": [
"**/components/ui/**",
"**/components.json"
]
}
}
}
Related skills
FAQ
How are shadcn components installed?
They are copied into your project with the shadcn CLI, not installed as dependencies, so you can modify them directly.
How are forms built?
With react-hook-form and zod validation using shadcn Form components.