shadcn/ui Setup
Skill Purpose: Component library initialization and customization patterns
Core Skill Pattern
Objective: Establish consistent component library setup with customizable components and design system integration.
Universal Pattern:
- •Define component library requirements and scope
- •Initialize component library and base components
- •Configure customization and theming patterns
- •Set up component organization and documentation
- •Create component usage and contribution guidelines
Key Decisions (Project-Specific):
- •Component library selection and scope
- •Customization level vs out-of-the-box usage
- •Design system integration approach
- •Component organization and naming conventions
- •Team contribution and maintenance processes
Project-Specific Implementation Notes
Customize per project:
- •Component selection based on UI requirements
- •Customization level based on design needs
- •Integration approach based on existing design system
- •Organization based on project complexity and team size
- •Documentation depth based on team experience
Example Implementation (shadcn/ui with Next.js Pattern)
Note: This is an example pattern using shadcn/ui. Adapt component library and configuration based on your specific project requirements and design needs.
Prerequisites (Example)
- •Project initialized
- •CSS framework configured
- •Component library selected
Example: shadcn/ui Component Library Implementation
Framework-Specific Example: This demonstrates the pattern using shadcn/ui with Next.js. Adapt for your component library and framework.
1. Install shadcn/ui CLI
# Install shadcn/ui CLI globally npm install -g shadcn-ui@latest # Or use npx to run without global installation npx shadcn-ui@latest init
2. Initialize shadcn/ui
# Initialize shadcn/ui in your project npx shadcn-ui@latest init
When prompted, use these settings:
- •Would you like to use TypeScript? → Yes
- •Which style would you like to use? → New York
- •Which color would you like to use as base color? → Slate
- •Where is your global CSS file? → src/app/globals.css
- •Would you like to use CSS variables for colors? → Yes
- •Where is your tailwind.config.js located? → tailwind.config.ts
- •Configure the import alias for components? → src/components
- •Configure the import alias for utils? → src/lib/utils
3. Update components.json
Update components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
4. Install Core Dependencies
# Install required dependencies npm install class-variance-authority clsx tailwind-merge lucide-react # Install additional dependencies for components npm install @radix-ui/react-slot @radix-ui/react-separator @radix-ui/react-label
5. Update Tailwind Configuration
Update tailwind.config.ts to include shadcn/ui paths:
import type { Config } from 'tailwindcss';
const config: Config = {
darkMode: ["class"],
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;
export default config;
6. Update Global CSS
Update src/app/globals.css with shadcn/ui styles:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
7. Create Core Utilities
Update src/lib/utils.ts:
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
8. Install Essential Components
# Install core UI components npx shadcn-ui@latest add button npx shadcn-ui@latest add card npx shadcn-ui@latest add input npx shadcn-ui@latest add label npx shadcn-ui@latest add textarea npx shadcn-ui@latest add select npx shadcn-ui@latest add checkbox npx shadcn-ui@latest add radio-group npx shadcn-ui@latest add switch npx shadcn-ui@latest add slider npx shadcn-ui@latest add progress npx shadcn-ui@latest add dialog npx shadcn-ui@latest add dropdown-menu npx shadcn-ui@latest add toast npx shadcn-ui@latest add table npx shadcn-ui@latest add badge npx shadcn-ui@latest add alert npx shadcn-ui@latest add separator npx shadcn-ui@latest add skeleton npx shadcn-ui@latest add tabs npx shadcn-ui@latest add accordion npx shadcn-ui@latest add collapsible npx shadcn-ui@latest add command npx shadcn-ui@latest add context-menu npx shadcn-ui@latest add hover-card npx shadcn-ui@latest add menubar npx shadcn-ui@latest add navigation-menu npx shadcn-ui@latest add popover npx shadcn-ui@latest add scroll-area npx shadcn-ui@latest add sheet npx shadcn-ui@latest add tooltip
9. Create Component Index
Create src/components/ui/index.ts:
// Re-export all UI components for easy importing export * from './button'; export * from './card'; export * from './input'; export * from './label'; export * from './textarea'; export * from './select'; export * from './checkbox'; export * from './radio-group'; export * from './switch'; export * from './slider'; export * from './progress'; export * from './dialog'; export * from './dropdown-menu'; export * from './toast'; export * from './table'; export * from './badge'; export * from './alert'; export * from './separator'; export * from './skeleton'; export * from './tabs'; export * from './accordion'; export * from './collapsible'; export * from './command'; export * from './context-menu'; export * from './hover-card'; export * from './menubar'; export * from './navigation-menu'; export * from './popover'; export * from './scroll-area'; export * from './sheet'; export * from './tooltip';
10. Create Custom Components
Create src/components/ui/extended-button.tsx:
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center 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',
{
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',
zeus: 'bg-zeus-500 text-white hover:bg-zeus-600',
zeusOutline: 'border border-zeus-500 text-zeus-500 hover:bg-zeus-50',
},
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',
xl: 'h-12 rounded-lg px-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ExtendedButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
}
const ExtendedButton = React.forwardRef<HTMLButtonElement, ExtendedButtonProps>(
({ className, variant, size, asChild = false, loading, children, disabled, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={disabled || loading}
{...props}
>
{loading ? (
<div className="flex items-center gap-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
{children}
</div>
) : (
children
)}
</Comp>
);
}
);
ExtendedButton.displayName = 'ExtendedButton';
export { ExtendedButton, buttonVariants };
11. Create Form Components
Create src/components/ui/form.tsx:
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
useFormContext,
} from 'react-hook-form';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
const Form = React.forwardRef<
HTMLFormElement,
React.FormHTMLAttributes<HTMLFormElement>
>(({ className, ...props }, ref) => {
return (
<form
ref={ref}
className={cn('space-y-6', className)}
{...props}
/>
);
});
Form.displayName = 'Form';
interface FormFieldContextValue<TFieldValues extends FieldValues> {
name: FieldPath<TFieldValues>;
}
const FormFieldContext = React.createContext<FormFieldContextValue<any>>(
{} as any
);
const FormField = <
TFieldValues extends FieldValues = FieldValues
>({
...props
}: ControllerProps<TFieldValues>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>');
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
interface FormItemContextValue {
id: string;
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as any
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = 'FormItem';
const FormLabel = React.forwardRef<
React.ElementRef<typeof Label>,
React.ComponentPropsWithoutRef<typeof Label>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && 'text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = 'FormLabel';
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = 'FormControl';
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
);
});
FormDescription.displayName = 'FormDescription';
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn('text-sm font-medium text-destructive', className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = 'FormMessage';
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};
12. Create Theme Provider
Create src/components/theme-provider.tsx:
'use client';
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
13. Update Package.json Scripts
Update package.json scripts:
{
"scripts": {
"shadcn:add": "shadcn-ui@latest add",
"shadcn:update": "shadcn-ui@latest update",
"shadcn:diff": "shadcn-ui@latest diff"
}
}
Code Examples
Using shadcn/ui Components
// Basic button usage
import { Button } from '@/components/ui/button';
export function ExampleButton() {
return (
<div className="flex gap-4">
<Button>Default Button</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
</div>
);
}
// Card component usage
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
export function ExampleCard() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card content goes here.</p>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</CardFooter>
</Card>
);
}
// Form with shadcn/ui
import { useForm } from 'react-hook-form';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
export function ExampleForm() {
const form = useForm();
return (
<Form {...form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Enter your username" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</Form>
);
}
Custom Component Extensions
// Extended button with loading state
import { ExtendedButton } from '@/components/ui/extended-button';
export function LoadingButtonExample() {
return (
<div className="flex gap-4">
<ExtendedButton loading>Loading...</ExtendedButton>
<ExtendedButton variant="zeus">Zeus Style</ExtendedButton>
<ExtendedButton size="xl">Extra Large</ExtendedButton>
</div>
);
}
// Custom card with hover effects
import { Card, CardContent } from '@/components/ui/card';
import { cn } from '@/lib/utils';
interface CustomCardProps extends React.HTMLAttributes<HTMLDivElement> {
hover?: boolean;
}
export function CustomCard({ className, hover, ...props }: CustomCardProps) {
return (
<Card
className={cn(
'transition-all duration-200',
hover && 'hover:shadow-lg hover:scale-105',
className
)}
{...props}
/>
);
}
Theme Integration
// Theme-aware component
'use client';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import { Moon, Sun } from 'lucide-react';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="outline"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
);
}
Configuration Templates
Complete components.json for Zeus Framework
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks",
"types": "@/types"
}
}
Best Practices
- •Use the CLI for component management - Ensures proper installation
- •Customize components thoughtfully - Maintain consistency
- •Use form components with react-hook-form - Type-safe forms
- •Implement dark mode properly - Use theme provider
- •Create reusable variants - Use class-variance-authority
- •Document custom components - Team collaboration
- •Keep components minimal - Single responsibility principle
- •Use proper TypeScript types - Type safety
Stop Conditions
STOP and report if:
- •shadcn/ui CLI fails to initialize
- •Component installation fails
- •Tailwind CSS conflicts occur
- •TypeScript errors in components
- •Theme switching doesn't work
Expected Outcomes:
- •All shadcn/ui components install correctly
- •Components render without errors
- •Theme switching works properly
- •TypeScript types are correct
- •Custom components extend base components
Verification Checklist
- • shadcn/ui CLI initializes successfully
- • All essential components install
- • Components render correctly
- • Dark mode functions properly
- • Form components work with react-hook-form
- • Custom components extend properly
- • TypeScript types are correct
- • Theme provider works
- • Component variants function
- • No CSS conflicts exist
Version: 1.0.0 Last Updated: 2026-01-31 Skill Category: Architecture - Scaffolding