Skip to main content Inicio Creadores codedogqby readany shadcn-ui
shadcn-ui Provides complete shadcn/ui component library patterns including installation, configuration, and implementation of accessible React components. Use when setting up shadcn/ui, installing components, building forms with React Hook Form and Zod, customizing themes with Tailwind CSS, or implementing UI patterns like buttons, dialogs, dropdowns, tables, and complex form layouts.
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/codedogQBY/ReadAny --skill shadcn-uiEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
Tauri v2 cross-platform app development with Rust backend. Use when configuring tauri.conf.json, implementing Rust commands (#[tauri::command]), setting up IPC patterns (invoke, emit, channels), configuring permissions/capabilities, troubleshooting build issues, or deploying desktop/mobile apps. Triggers on Tauri, src-tauri, invoke, emit, capabilities.json.
Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
Explorador de archivos
6 archivos name shadcn-ui description Provides complete shadcn/ui component library patterns including installation, configuration, and implementation of accessible React components. Use when setting up shadcn/ui, installing components, building forms with React Hook Form and Zod, customizing themes with Tailwind CSS, or implementing UI patterns like buttons, dialogs, dropdowns, tables, and complex form layouts. allowed-tools Read, Write, Bash, Edit, Glob
shadcn/ui Component Patterns
Overview
Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS. This skill provides comprehensive patterns for implementing production-ready components with full accessibility support.
Table of Contents
When to Use
Setting up a new project with shadcn/ui
Installing or configuring individual components
Building forms with React Hook Form and Zod validation
Creating accessible UI components (buttons, dialogs, dropdowns, sheets)
Customizing component styling with Tailwind CSS
Implementing design systems with shadcn/ui
Building Next.js applications with TypeScript
Creating complex layouts and data displays
Instructions
Initialize Project : Run npx shadcn@latest init to configure shadcn/ui
Install Components : Add components with npx shadcn@latest add <component>
Configure Theme : Customize CSS variables in globals.css for theming
Import Components : Use components from @/components/ui/ directory
Customize as Needed : Modify component code directly in your project
Add Form Validation : Integrate React Hook Form with Zod schemas
Test Accessibility : Verify ARIA attributes and keyboard navigation
Examples
Complete Form with Validation "use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Form , FormControl , FormField , FormItem , FormLabel , FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object ({
email : z.string ().email ("Invalid email" ),
password : z.string ().min (8 , "Password must be at least 8 characters" ),
})
export function LoginForm ( ) {
const form = useForm<z.infer <typeof formSchema>>({
resolver : zodResolver (formSchema),
defaultValues : { email : "" , password : "" },
})
return (
<Form {...form }>
<form onSubmit ={form.handleSubmit(console.log)} className ="space-y-4" >
<FormField name ="email" render ={({ field }) => (
<FormItem >
<FormLabel > Email</FormLabel >
<FormControl > <Input type ="email" {...field } /> </FormControl >
<FormMessage />
</FormItem >
)} />
<Button type ="submit" > Login</Button >
</form >
</Form >
)
}
Constraints and Warnings
Not an NPM Package : Components are copied to your project; you own the code
Client Components : Most components require "use client" directive
Radix Dependencies : Ensure all @radix-ui packages are installed
Tailwind Required : Components rely on Tailwind CSS utilities
TypeScript : Designed for TypeScript projects; type definitions included
Path Aliases : Configure @ alias in tsconfig.json for imports
Dark Mode : Set up dark mode with CSS variables or class strategy
Quick Start For new projects, use the automated setup:
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn@latest init
npx shadcn@latest add button input form card dialog select
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
npx shadcn@latest init
What is shadcn/ui? shadcn/ui is not a traditional component library or npm package. Instead:
It's a collection of reusable components that you can copy into your project
Components are yours to customize - you own the code
Built with Radix UI primitives for accessibility
Styled with Tailwind CSS utilities
Includes CLI tool for easy component installation
Installation & Setup
Initial Setup During setup, you'll configure:
TypeScript or JavaScript
Style (Default, New York, etc.)
Base color theme
CSS variables or Tailwind CSS classes
Component installation path
Installing Individual Components
npx shadcn@latest add button
npx shadcn@latest add button input form
npx shadcn@latest add --all
Manual Installation If you prefer manual setup:
npm install @radix-ui/react-slot
Project Configuration
Required Dependencies {
"dependencies" : {
"@radix-ui/react-accordion" : "^1.1.2" ,
"@radix-ui/react-alert-dialog" : "^1.0.5" ,
"@radix-ui/react-dialog" : "^1.0.5" ,
"@radix-ui/react-dropdown-menu" : "^2.0.6" ,
"@radix-ui/react-label" : "^2.0.2" ,
"@radix-ui/react-select" : "^2.0.0" ,
"@radix-ui/react-separator" : "^1.0.3" ,
"@radix-ui/react-slot" : "^1.0.2" ,
"@radix-ui/react-toast" : "^1.1.5" ,
"class-variance-authority" : "^0.7.0" ,
"clsx" : "^2.0.0" ,
"lucide-react" : "^0.294.0" ,
"tailwind-merge" : "^2.0.0" ,
"tailwindcss-animate" : "^1.0.7"
}
}
TSConfig Configuration {
"compilerOptions" : {
"target" : "es5" ,
"lib" : [ "dom" , "dom.iterable" , "es6" ] ,
"allowJs" : true ,
"skipLibCheck" : true ,
"strict" : true ,
"forceConsistentCasingInFileNames" : true ,
"noEmit" : true ,
"esModuleInterop" : true ,
"module" : "esnext" ,
"moduleResolution" : "node" ,
"resolveJsonModule" : true ,
"isolatedModules" : true ,
"jsx" : "preserve" ,
"incremental" : true ,
"plugins" : [
{
"name" : "next"
}
] ,
"baseUrl" : "." ,
"paths" : {
"@/components/*" : [ "./src/components/*" ] ,
"@/lib/*" : [ "./src/lib/*" ]
}
} ,
"include" : [ "next-env.d.ts" , "**/*.ts" , "**/*.tsx" , ".next/types/**/*.ts" ] ,
"exclude" : [ "node_modules" ]
}
Tailwind Configuration
module .exports = {
darkMode : ["class" ],
content : [
'./pages/**/*.{ts,tsx}' ,
'./components/**/*.{ts,tsx}' ,
'./app/**/*.{ts,tsx}' ,
'./src/**/*.{ts,tsx}' ,
],
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" )],
}
CSS Variables (globals.css) @tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background : 0 0% 100% ;
--foreground : 222.2 84% 4.9% ;
--card : 0 0% 100% ;
--card-foreground : 222.2 84% 4.9% ;
--popover : 0 0% 100% ;
--popover-foreground : 222.2 84% 4.9% ;
--primary : 222.2 47.4% 11.2% ;
--primary-foreground : 210 40% 98% ;
--secondary : 210 40% 96.1% ;
--secondary-foreground : 222.2 47.4% 11.2% ;
--muted : 210 40% 96.1% ;
--muted-foreground : 215.4 16.3% 46.9% ;
--accent : 210 40% 96.1% ;
--accent-foreground : 222.2 47.4% 11.2% ;
--destructive : 0 84.2% 60.2% ;
--destructive-foreground : 210 40% 98% ;
--border : 214.3 31.8% 91.4% ;
--input : 214.3 31.8% 91.4% ;
--ring : 222.2 84% 4.9% ;
--radius : 0.5rem ;
}
.dark {
--background : 222.2 84% 4.9% ;
--foreground : 210 40% 98% ;
--card : 222.2 84% 4.9% ;
--card-foreground : 210 40% 98% ;
--popover : 222.2 84% 4.9% ;
--popover-foreground : 210 40% 98% ;
--primary : 210 40% 98% ;
--primary-foreground : 222.2 47.4% 11.2% ;
--secondary : 217.2 32.6% 17.5% ;
--secondary-foreground : 210 40% 98% ;
--muted : 217.2 32.6% 17.5% ;
--muted-foreground : 215 20.2% 65.1% ;
--accent : 217.2 32.6% 17.5% ;
--accent-foreground : 210 40% 98% ;
--destructive : 0 62.8% 30.6% ;
--destructive-foreground : 210 40% 98% ;
--border : 217.2 32.6% 17.5% ;
--input : 217.2 32.6% 17.5% ;
--ring : 212.7 26.8% 83.9% ;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
Core Components
Button Component npx shadcn@latest add button
import { Button } from "@/components/ui/button" ;
export function ButtonDemo ( ) {
return <Button > Click me</Button > ;
}
import { Button } from "@/components/ui/button" ;
export function ButtonVariants ( ) {
return (
<div className ="flex gap-4" >
<Button variant ="default" > Default</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 >
);
}
<div className="flex gap-4 items-center" >
<Button size ="default" > Default</Button >
<Button size ="sm" > Small</Button >
<Button size ="lg" > Large</Button >
<Button size ="icon" >
<Icon className ="h-4 w-4" />
</Button >
</div>
import { Button } from "@/components/ui/button" ;
import { Loader2 } from "lucide-react" ;
export function ButtonLoading ( ) {
return (
<Button disabled >
<Loader2 className ="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button >
);
}
Input & Form Fields
Input Component npx shadcn@latest add input
import { Input } from "@/components/ui/input" ;
export function InputDemo ( ) {
return <Input type ="email" placeholder ="Email" /> ;
}
import { Input } from "@/components/ui/input" ;
import { Label } from "@/components/ui/label" ;
export function InputWithLabel ( ) {
return (
<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" />
</div >
);
}
import { Button } from "@/components/ui/button" ;
import { Input } from "@/components/ui/input" ;
export function InputWithButton ( ) {
return (
<div className ="flex w-full max-w-sm items-center gap-2" >
<Input type ="email" placeholder ="Email" />
<Button type ="submit" variant ="outline" > Subscribe</Button >
</div >
);
}
Forms with Validation npx shadcn@latest add form
This installs React Hook Form, Zod, and form components.
"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"
import { toast } from "@/components/ui/use-toast"
const formSchema = z.object ({
username : z.string ().min (2 , {
message : "Username must be at least 2 characters." ,
}),
email : z.string ().email ({
message : "Please enter a valid email address." ,
}),
})
export function ProfileForm ( ) {
const form = useForm<z.infer <typeof formSchema>>({
resolver : zodResolver (formSchema),
defaultValues : {
username : "" ,
email : "" ,
},
})
function onSubmit (values : z.infer<typeof formSchema> ) {
toast ({
title : "You submitted the following values:" ,
description : (
<pre className ="mt-2 w-[340px] rounded-md bg-slate-950 p-4" >
<code className ="text-white" > {JSON.stringify(values, null, 2)}</code >
</pre >
),
})
}
return (
<Form {...form }>
<form onSubmit ={form.handleSubmit(onSubmit)} className ="space-y-8" >
<FormField
control ={form.control}
name ="username"
render ={({ field }) => (
<FormItem >
<FormLabel > Username</FormLabel >
<FormControl >
<Input placeholder ="shadcn" {...field } />
</FormControl >
<FormDescription >
This is your public display name.
</FormDescription >
<FormMessage />
</FormItem >
)}
/>
<FormField
control ={form.control}
name ="email"
render ={({ field }) => (
<FormItem >
<FormLabel > Email</FormLabel >
)}
/>
Submit
)
}
Card Component npx shadcn@latest add card
import {
Card ,
CardContent ,
CardDescription ,
CardFooter ,
CardHeader ,
CardTitle ,
} from "@/components/ui/card"
export function CardDemo ( ) {
return (
<Card >
<CardHeader >
<CardTitle > Card Title</CardTitle >
<CardDescription > Card Description</CardDescription >
</CardHeader >
<CardContent >
<p > Card Content</p >
</CardContent >
<CardFooter >
<p > Card Footer</p >
</CardFooter >
</Card >
)
}
import { Button } from "@/components/ui/button"
import {
Card ,
CardContent ,
CardDescription ,
CardFooter ,
CardHeader ,
CardTitle ,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function CardWithForm ( ) {
return (
<Card className ="w-[350px]" >
<CardHeader >
<CardTitle > Create project</CardTitle >
<CardDescription > Deploy your new project in one-click.</CardDescription >
</CardHeader >
<CardContent >
<form >
<div className ="grid w-full items-center gap-4" >
<div className ="flex flex-col space-y-1.5" >
<Label htmlFor ="name" > Name</Label >
<Input id ="name" placeholder ="Name of your project" />
</div >
</div >
</form >
</CardContent >
<CardFooter className ="flex justify-between" >
<Button variant ="outline" > Cancel</Button >
<Button > Deploy</Button >
</CardFooter >
</Card >
)
}
Dialog (Modal) Component npx shadcn@latest add dialog
import { Button } from "@/components/ui/button"
import {
Dialog ,
DialogContent ,
DialogDescription ,
DialogFooter ,
DialogHeader ,
DialogTitle ,
DialogTrigger ,
} from "@/components/ui/dialog"
export function DialogDemo ( ) {
return (
<Dialog >
<DialogTrigger asChild >
<Button variant ="outline" > Open Dialog</Button >
</DialogTrigger >
<DialogContent className ="sm:max-w-[425px]" >
<DialogHeader >
<DialogTitle > Edit profile</DialogTitle >
<DialogDescription >
Make changes to your profile here. Click save when you're done.
</DialogDescription >
</DialogHeader >
<div className ="grid gap-4 py-4" >
<div className ="grid grid-cols-4 items-center gap-4" >
<Label htmlFor ="name" className ="text-right" >
Name
</Label >
<Input id ="name" value ="Pedro Duarte" className ="col-span-3" />
</div >
</div >
<DialogFooter >
<Button type ="submit" > Save changes</Button >
</DialogFooter >
</DialogContent >
</Dialog >
)
}
Sheet (Slide-over) Component npx shadcn@latest add sheet
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 Sheet</Button >
</SheetTrigger >
<SheetContent >
<SheetHeader >
<SheetTitle > Edit profile</SheetTitle >
<SheetDescription >
Make changes to your profile here. Click save when you're done.
</SheetDescription >
</SheetHeader >
<div className ="grid gap-4 py-4" >
<div className ="grid grid-cols-4 items-center gap-4" >
<Label htmlFor ="name" className ="text-right" >
Name
</Label >
<Input id ="name" value ="Pedro Duarte" className ="col-span-3" />
</div >
<div className ="grid grid-cols-4 items-center gap-4" >
<Label htmlFor ="username" className ="text-right" >
Username
</Label >
<Input id ="username" value ="@peduarte" className ="col-span-3" />
</div >
</div >
</SheetContent >
</Sheet >
)
}
Sheet with side placement:
<Sheet >
<SheetTrigger asChild >
<Button variant ="outline" > Open Right Sheet</Button >
</SheetTrigger >
<SheetContent side ="right" >
<SheetHeader >
<SheetTitle > Settings</SheetTitle >
<SheetDescription >
Configure your application settings here.
</SheetDescription >
</SheetHeader >
{/* Settings content */}
</SheetContent >
</Sheet >
Menubar & Navigation
Menubar Component npx shadcn@latest add menubar
import {
Menubar ,
MenubarContent ,
MenubarItem ,
MenubarMenu ,
MenubarSeparator ,
MenubarShortcut ,
MenubarSub ,
MenubarSubContent ,
MenubarSubTrigger ,
MenubarTrigger ,
} from "@/components/ui/menubar"
export function MenubarDemo ( ) {
return (
<Menubar >
<MenubarMenu >
<MenubarTrigger > File</MenubarTrigger >
<MenubarContent >
<MenubarItem >
New Tab <MenubarShortcut > ⌘T</MenubarShortcut >
</MenubarItem >
<MenubarItem >
New Window <MenubarShortcut > ⌘N</MenubarShortcut >
</MenubarItem >
<MenubarSeparator />
<MenubarItem > Share</MenubarItem >
<MenubarSeparator />
<MenubarItem > Print</MenubarItem >
</MenubarContent >
</MenubarMenu >
<MenubarMenu >
<MenubarTrigger > Edit</MenubarTrigger >
<MenubarContent >
<MenubarItem >
Undo <MenubarShortcut > ⌘Z</MenubarShortcut >
</MenubarItem >
<MenubarItem >
Redo <MenubarShortcut > ⌘Y</MenubarShortcut >
</MenubarItem >
<MenubarSeparator />
<MenubarSub >
<MenubarSubTrigger > Find</MenubarSubTrigger >
<MenubarSubContent >
<MenubarItem > Search the web</MenubarItem >
<MenubarItem > Find...</MenubarItem >
<MenubarItem > Find Next</MenubarItem >
<MenubarItem > Find Previous</MenubarItem >
</MenubarSubContent >
</MenubarSub >
</MenubarContent >
</MenubarMenu >
</Menubar >
)
}
Select (Dropdown) Component npx shadcn@latest add select
import {
Select ,
SelectContent ,
SelectItem ,
SelectTrigger ,
SelectValue ,
} from "@/components/ui/select"
export function SelectDemo ( ) {
return (
<Select >
<SelectTrigger className ="w-[180px]" >
<SelectValue placeholder ="Select a fruit" />
</SelectTrigger >
<SelectContent >
<SelectItem value ="apple" > Apple</SelectItem >
<SelectItem value ="banana" > Banana</SelectItem >
<SelectItem value ="orange" > Orange</SelectItem >
</SelectContent >
</Select >
)
}
<FormField
control={form.control }
name="role"
render={({ field } ) => (
<FormItem >
<FormLabel > Role</FormLabel >
<Select onValueChange ={field.onChange} defaultValue ={field.value} >
<FormControl >
<SelectTrigger >
<SelectValue placeholder ="Select a role" />
</SelectTrigger >
</FormControl >
<SelectContent >
<SelectItem value ="admin" > Admin</SelectItem >
<SelectItem value ="user" > User</SelectItem >
<SelectItem value ="guest" > Guest</SelectItem >
</SelectContent >
</Select >
<FormMessage />
</FormItem >
)}
/>
Toast Notifications npx shadcn@latest add toast
Setup toast provider in root layout:
import { Toaster } from "@/components/ui/toaster"
export default function RootLayout ({ children } ) {
return (
<html lang ="en" >
<body >
{children}
<Toaster />
</body >
</html >
)
}
import { useToast } from "@/components/ui/use-toast"
import { Button } from "@/components/ui/button"
export function ToastDemo ( ) {
const { toast } = useToast ()
return (
<Button
onClick ={() => {
toast({
title: "Scheduled: Catch up",
description: "Friday, February 10, 2023 at 5:57 PM",
})
}}
>
Show Toast
</Button >
)
}
toast ({
title : "Success" ,
description : "Your changes have been saved." ,
})
toast ({
variant : "destructive" ,
title : "Error" ,
description : "Something went wrong." ,
})
toast ({
title : "Uh oh! Something went wrong." ,
description : "There was a problem with your request." ,
action : <ToastAction altText ="Try again" > Try again</ToastAction > ,
})
Table Component npx shadcn@latest add table
import {
Table ,
TableBody ,
TableCaption ,
TableCell ,
TableHead ,
TableHeader ,
TableRow ,
} from "@/components/ui/table"
const invoices = [
{ invoice : "INV001" , status : "Paid" , method : "Credit Card" , amount : "$250.00" },
{ invoice : "INV002" , status : "Pending" , method : "PayPal" , amount : "$150.00" },
]
export function TableDemo ( ) {
return (
<Table >
<TableCaption > A list of your recent invoices.</TableCaption >
<TableHeader >
<TableRow >
<TableHead > Invoice</TableHead >
<TableHead > Status</TableHead >
<TableHead > Method</TableHead >
<TableHead className ="text-right" > Amount</TableHead >
</TableRow >
</TableHeader >
<TableBody >
{invoices.map((invoice) => (
<TableRow key ={invoice.invoice} >
<TableCell className ="font-medium" > {invoice.invoice}</TableCell >
<TableCell > {invoice.status}</TableCell >
<TableCell > {invoice.method}</TableCell >
<TableCell className ="text-right" > {invoice.amount}</TableCell >
</TableRow >
))}
</TableBody >
</Table >
)
}
Charts Component npx shadcn@latest add chart
The charts component in shadcn/ui is built on Recharts - providing direct access to all Recharts capabilities with consistent theming and styling.
ChartContainer and ChartConfig The ChartContainer wraps your Recharts component and accepts a config prop for theming:
import { Bar , BarChart , CartesianGrid , XAxis , YAxis } from "recharts"
import { ChartContainer , ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
desktop : {
label : "Desktop" ,
color : "var(--chart-1)" ,
},
mobile : {
label : "Mobile" ,
color : "var(--chart-2)" ,
},
} satisfies import ("@/components/ui/chart" ).ChartConfig
const chartData = [
{ month : "January" , desktop : 186 , mobile : 80 },
{ month : "February" , desktop : 305 , mobile : 200 },
{ month : "March" , desktop : 237 , mobile : 120 },
]
export function BarChartDemo ( ) {
return (
<ChartContainer config ={chartConfig} className ="min-h-[200px] w-full" >
<BarChart data ={chartData} >
<CartesianGrid vertical ={false} />
<XAxis
dataKey ="month"
tickLine ={false}
axisLine ={false}
tickFormatter ={(value) => value.slice(0, 3)}
/>
<Bar
dataKey ="desktop"
fill ="var(--color-desktop)"
radius ={4}
/>
<Bar
dataKey ="mobile"
fill ="var(--color-mobile)"
radius ={4}
/>
<ChartTooltip content ={ <ChartTooltipContent /> } />
</BarChart >
</ChartContainer >
)
}
ChartConfig with Custom Colors You can define custom colors directly in the configuration:
const chartConfig = {
visitors : {
label : "Visitors" ,
color : "#2563eb" ,
theme : {
light : "#2563eb" ,
dark : "#60a5fa" ,
},
},
sales : {
label : "Sales" ,
color : "var(--chart-1)" ,
theme : {
light : "oklch(0.646 0.222 41.116)" ,
dark : "oklch(0.696 0.182 281.41)" ,
},
},
} satisfies import ("@/components/ui/chart" ).ChartConfig
CSS Variables for Charts Add chart color variables to your globals.css:
@layer base {
:root {
--chart-1 : oklch (0.646 0.222 41.116 );
--chart-2 : oklch (0.6 0.118 184.704 );
--chart-3 : oklch (0.546 0.198 38.228 );
--chart-4 : oklch (0.596 0.151 343.253 );
--chart-5 : oklch (0.546 0.158 49.157 );
}
.dark {
--chart-1 : oklch (0.488 0.243 264.376 );
--chart-2 : oklch (0.696 0.17 162.48 );
--chart-3 : oklch (0.698 0.141 24.311 );
--chart-4 : oklch (0.676 0.172 171.196 );
--chart-5 : oklch (0.578 0.192 302.85 );
}
}
Line Chart Example import { Line , LineChart , CartesianGrid , XAxis , YAxis } from "recharts"
import { ChartContainer , ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
price : {
label : "Price" ,
color : "var(--chart-1)" ,
},
} satisfies import ("@/components/ui/chart" ).ChartConfig
const chartData = [
{ month : "January" , price : 186 },
{ month : "February" , price : 305 },
{ month : "March" , price : 237 },
{ month : "April" , price : 203 },
{ month : "May" , price : 276 },
]
export function LineChartDemo ( ) {
return (
<ChartContainer config ={chartConfig} className ="min-h-[200px]" >
<LineChart data ={chartData} >
<CartesianGrid vertical ={false} />
<XAxis dataKey ="month" tickLine ={false} axisLine ={false} />
<YAxis tickLine ={false} axisLine ={false} tickFormatter ={(value) => `$${value}`} />
<Line
dataKey ="price"
stroke ="var(--color-price)"
strokeWidth ={2}
dot ={false}
/>
<ChartTooltip content ={ <ChartTooltipContent /> } />
</LineChart >
</ChartContainer >
)
}
Area Chart Example import { Area , AreaChart , XAxis , YAxis } from "recharts"
import { ChartContainer , ChartLegend , ChartLegendContent , ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
desktop : { label : "Desktop" , color : "var(--chart-1)" },
mobile : { label : "Mobile" , color : "var(--chart-2)" },
} satisfies import ("@/components/ui/chart" ).ChartConfig
export function AreaChartDemo ( ) {
return (
<ChartContainer config ={chartConfig} className ="min-h-[200px]" >
<AreaChart data ={chartData} >
<XAxis dataKey ="month" tickLine ={false} axisLine ={false} />
<YAxis tickLine ={false} axisLine ={false} />
<Area
dataKey ="desktop"
fill ="var(--color-desktop)"
stroke ="var(--color-desktop)"
fillOpacity ={0.3}
/>
<Area
dataKey ="mobile"
fill ="var(--color-mobile)"
stroke ="var(--color-mobile)"
fillOpacity ={0.3}
/>
<ChartTooltip content ={ <ChartTooltipContent /> } />
<ChartLegend content ={ <ChartLegendContent /> } />
</AreaChart >
</ChartContainer >
)
}
Pie Chart Example import { Pie , PieChart } from "recharts"
import { ChartContainer , ChartLegend , ChartLegendContent , ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
chrome : { label : "Chrome" , color : "var(--chart-1)" },
safari : { label : "Safari" , color : "var(--chart-2)" },
firefox : { label : "Firefox" , color : "var(--chart-3)" },
} satisfies import ("@/components/ui/chart" ).ChartConfig
const pieData = [
{ browser : "Chrome" , visitors : 275 , fill : "var(--color-chrome)" },
{ browser : "Safari" , visitors : 200 , fill : "var(--color-safari)" },
{ browser : "Firefox" , visitors : 187 , fill : "var(--color-firefox)" },
]
export function PieChartDemo ( ) {
return (
<ChartContainer config ={chartConfig} className ="min-h-[200px]" >
<PieChart >
<Pie
data ={pieData}
dataKey ="visitors"
nameKey ="browser"
cx ="50%"
cy ="50%"
outerRadius ={80}
/>
<ChartTooltip content ={ <ChartTooltipContent /> } />
<ChartLegend content ={ <ChartLegendContent /> } />
</PieChart >
</ChartContainer >
)
}
ChartTooltipContent Props Prop Type Default Description labelKeystring "label" Key for tooltip label nameKeystring "name" Key for tooltip name indicator"dot" | "line" | "dashed" "dot" Indicator style hideLabelboolean false Hide label hideIndicatorboolean false Hide indicator
Accessibility Enable keyboard navigation and screen reader support:
<BarChart accessibilityLayer data={chartData}>...</BarChart >
Keyboard arrow key navigation
ARIA labels for chart elements
Screen reader announcements for data values
Customization
Theming with CSS Variables shadcn/ui uses CSS variables for theming. Configure in globals.css:
@layer base {
:root {
--background : 0 0% 100% ;
--foreground : 222.2 84% 4.9% ;
--primary : 222.2 47.4% 11.2% ;
--primary-foreground : 210 40% 98% ;
--secondary : 210 40% 96.1% ;
--secondary-foreground : 222.2 47.4% 11.2% ;
--muted : 210 40% 96.1% ;
--muted-foreground : 215.4 16.3% 46.9% ;
--accent : 210 40% 96.1% ;
--accent-foreground : 222.2 47.4% 11.2% ;
--destructive : 0 84.2% 60.2% ;
--destructive-foreground : 210 40% 98% ;
--border : 214.3 31.8% 91.4% ;
--input : 214.3 31.8% 91.4% ;
--ring : 222.2 84% 4.9% ;
--radius : 0.5rem ;
}
.dark {
--background : 222.2 84% 4.9% ;
--foreground : 210 40% 98% ;
--primary : 210 40% 98% ;
--primary-foreground : 222.2 47.4% 11.2% ;
}
}
Customizing Components Since you own the code, customize directly:
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 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" ,
outline : "border border-input bg-background hover:bg-accent" ,
custom : "bg-gradient-to-r from-purple-500 to-pink-500 text-white" ,
},
size : {
default : "h-10 px-4 py-2" ,
sm : "h-9 rounded-md px-3" ,
lg : "h-11 rounded-md px-8" ,
xl : "h-14 rounded-md px-10 text-lg" ,
},
},
defaultVariants : {
variant : "default" ,
size : "default" ,
},
}
)
export interface ButtonProps
extends React .ButtonHTMLAttributes <HTMLButtonElement >,
VariantProps <typeof buttonVariants> {
asChild ?: boolean
}
const Button = React .forwardRef <HTMLButtonElement , ButtonProps >(
({ className, variant, size, asChild = false , ...props }, ref ) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className ={cn(buttonVariants({ variant , size , className }))}
ref ={ref}
{...props }
/>
)
}
)
Button .displayName = "Button"
export { Button , buttonVariants }
Next.js Integration
App Router Setup For Next.js 13+ with App Router, ensure components use "use client" directive:
"use client"
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"
Layout Integration Add the Toaster to your root layout:
import { Toaster } from "@/components/ui/toaster"
import "./globals.css"
export default function RootLayout ({
children,
}: {
children: React.ReactNode
} ) {
return (
<html lang ="en" suppressHydrationWarning >
<body className ="min-h-screen bg-background font-sans antialiased" >
{children}
<Toaster />
</body >
</html >
)
}
Server Components When using shadcn/ui components in Server Components, wrap them in a Client Component:
import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card"
import { ButtonClient } from "@/components/ui/button-client"
export default function DashboardPage ( ) {
return (
<div className ="container mx-auto p-6" >
<Card >
<CardHeader >
<CardTitle > Dashboard</CardTitle >
</CardHeader >
<CardContent >
<ButtonClient > Interactive Button</ButtonClient >
</CardContent >
</Card >
</div >
)
}
"use client"
import { Button } from "./button"
export function ButtonClient (props : React .ComponentProps <typeof Button > ) {
return <Button {...props } />
}
Route Handlers with Forms Create API routes for form submissions:
import { NextRequest , NextResponse } from "next/server"
import { z } from "zod"
const contactSchema = z.object ({
name : z.string ().min (2 ),
email : z.string ().email (),
message : z.string ().min (10 ),
})
export async function POST (request : NextRequest ) {
try {
const body = await request.json ()
const validated = contactSchema.parse (body)
console .log ("Form submission:" , validated)
return NextResponse .json ({ success : true })
} catch (error) {
if (error instanceof z.ZodError ) {
return NextResponse .json (
{ errors : error.errors },
{ status : 400 }
)
}
return NextResponse .json (
{ error : "Internal server error" },
{ status : 500 }
)
}
}
Form with Server Action Using Next.js 14+ Server Actions:
"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 ,
FormField ,
FormItem ,
FormLabel ,
FormMessage ,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
const formSchema = z.object ({
name : z.string ().min (2 ),
email : z.string ().email (),
message : z.string ().min (10 ),
})
async function onSubmit (values : z.infer<typeof formSchema> ) {
try {
const response = await fetch ("/api/contact" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" },
body : JSON .stringify (values),
})
if (!response.ok ) throw new Error ("Failed to submit" )
toast ({
title : "Success!" ,
description : "Your message has been sent." ,
})
} catch (error) {
toast ({
variant : "destructive" ,
title : "Error" ,
description : "Failed to send message. Please try again." ,
})
}
}
export default function ContactPage ( ) {
const form = useForm<z.infer <typeof formSchema>>({
resolver : zodResolver (formSchema),
})
return (
<div className ="container mx-auto max-w-2xl py-8" >
<Form {...form }>
<form onSubmit ={form.handleSubmit(onSubmit)} className ="space-y-6" >
<FormField
control ={form.control}
name ="name"
render ={({ field }) => (
<FormItem >
<FormLabel > Name</FormLabel >
<FormControl >
<Input placeholder ="Your name" {...field } />
</FormControl >
<FormMessage />
</FormItem >
)}
/>
<FormField
control ={form.control}
name ="email"
render ={({ field }) => (
<FormItem >
<FormLabel > Email</FormLabel >
)}
/>
(
Message
)}
/>
Send Message
)
}
Metadata with shadcn/ui Using shadcn/ui components in metadata:
import { Metadata } from "next"
export const metadata : Metadata = {
title : {
default : "My App" ,
template : "%s | My App" ,
},
description : "Built with shadcn/ui and Next.js" ,
}
import { Metadata } from "next"
import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card"
export const metadata : Metadata = {
title : "About Us" ,
description : "Learn more about our company" ,
}
export default function AboutPage ( ) {
return (
<div className ="container mx-auto py-8" >
<Card >
<CardHeader >
<CardTitle > About Our Company</CardTitle >
</CardHeader >
<CardContent >
<p > We build amazing products with modern web technologies.</p >
</CardContent >
</Card >
</div >
)
}
Font Optimization Optimize fonts with next/font:
import { Inter } from "next/font/google"
import { Toaster } from "@/components/ui/toaster"
import { cn } from "@/lib/utils"
import "./globals.css"
const inter = Inter ({ subsets : ["latin" ] })
export default function RootLayout ({
children,
}: {
children: React.ReactNode
} ) {
return (
<html lang ="en" suppressHydrationWarning >
<body className ={cn( "min-h-screen bg-background font-sans antialiased ", inter.className )}>
{children}
<Toaster />
</body >
</html >
)
}
Advanced Patterns
Form with Multiple Fields const formSchema = z.object ({
username : z.string ().min (2 ).max (50 ),
email : z.string ().email (),
bio : z.string ().max (160 ).min (4 ),
role : z.enum (["admin" , "user" , "guest" ]),
notifications : z.boolean ().default (false ),
})
export function AdvancedForm ( ) {
const form = useForm<z.infer <typeof formSchema>>({
resolver : zodResolver (formSchema),
defaultValues : {
username : "" ,
email : "" ,
bio : "" ,
role : "user" ,
notifications : false ,
},
})
function onSubmit (values : z.infer<typeof formSchema> ) {
console .log (values)
}
return (
<Form {...form }>
<form onSubmit ={form.handleSubmit(onSubmit)} className ="space-y-8" >
{/* Username field */}
<FormField
control ={form.control}
name ="username"
render ={({ field }) => (
<FormItem >
<FormLabel > Username</FormLabel >
<FormControl >
<Input placeholder ="johndoe" {...field } />
</FormControl >
<FormMessage />
</FormItem >
)}
/>
{/* Email field */}
<FormField
control ={form.control}
name ="email"
render ={({ field }) => (
<FormItem >
<FormLabel > Email</FormLabel >
<FormControl >
<Input = = { } />
)}
/>
{/* Textarea field */}
(
Bio
)}
/>
{/* Select field */}
(
Role
Admin
User
Guest
)}
/>
{/* Checkbox field */}
(
Email notifications
Receive emails about your account activity.
)}
/>
Submit
)
}
Best Practices
Accessibility : Components use Radix UI primitives for ARIA compliance
Customization : Modify components directly in your codebase
Type Safety : Use TypeScript for type-safe props and state
Validation : Use Zod schemas for form validation
Styling : Leverage Tailwind utilities and CSS variables
Consistency : Use the same component patterns across your app
Testing : Components are testable with React Testing Library
Performance : Components are optimized and tree-shakeable
Common Component Combinations
Login Form <Card className="w-[350px]" >
<CardHeader >
<CardTitle > Login</CardTitle >
<CardDescription > Enter your credentials to continue</CardDescription >
</CardHeader >
<CardContent >
<Form {...form }>
<form onSubmit ={form.handleSubmit(onSubmit)} className ="space-y-4" >
<FormField
control ={form.control}
name ="email"
render ={({ field }) => (
<FormItem >
<FormLabel > Email</FormLabel >
<FormControl >
<Input type ="email" placeholder ="you@example.com" {...field } />
</FormControl >
<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" className ="w-full" > Login</Button >
</form >
</Form >
</CardContent >
</Card >
References <FormControl >
<Input type ="email" placeholder ="you@example.com" {...field } />
</FormControl >
<FormMessage />
</FormItem >
<Button type ="submit" >
</Button >
</form >
</Form >
<FormControl >
<Input type ="email" placeholder ="your@email.com" {...field } />
</FormControl >
<FormMessage />
</FormItem >
<FormField
control ={form.control}
name ="message"
render ={({ field }) =>
<FormItem >
<FormLabel >
</FormLabel >
<FormControl >
<Textarea
placeholder ="Your message..."
className ="resize-none"
{...field }
/>
</FormControl >
<FormMessage />
</FormItem >
<Button type ="submit" className ="w-full" >
</Button >
</form >
</Form >
</div >
type
"email"
placeholder
"john@example.com"
...field
</FormControl >
<FormMessage />
</FormItem >
<FormField
control ={form.control}
name ="bio"
render ={({ field }) =>
<FormItem >
<FormLabel >
</FormLabel >
<FormControl >
<Textarea
placeholder ="Tell us about yourself"
className ="resize-none"
{...field }
/>
</FormControl >
<FormMessage />
</FormItem >
<FormField
control ={form.control}
name ="role"
render ={({ field }) =>
<FormItem >
<FormLabel >
</FormLabel >
<Select onValueChange ={field.onChange} defaultValue ={field.value} >
<FormControl >
<SelectTrigger >
<SelectValue placeholder ="Select a role" />
</SelectTrigger >
</FormControl >
<SelectContent >
<SelectItem value ="admin" >
</SelectItem >
<SelectItem value ="user" >
</SelectItem >
<SelectItem value ="guest" >
</SelectItem >
</SelectContent >
</Select >
<FormMessage />
</FormItem >
<FormField
control ={form.control}
name ="notifications"
render ={({ field }) =>
<FormItem className ="flex flex-row items-start space-x-3 space-y-0" >
<FormControl >
<Checkbox
checked ={field.value}
onCheckedChange ={field.onChange}
/>
</FormControl >
<div className ="space-y-1 leading-none" >
<FormLabel >
</FormLabel >
<FormDescription >
</FormDescription >
</div >
</FormItem >
<Button type ="submit" >
</Button >
</form >
</Form >