Building Beautiful UIs with shadcn/ui
shadcn/ui gives you beautifully designed, accessible components that you own and control. Here's how to set it up and start building.
shadcn/ui has taken the React ecosystem by storm — and for good reason. Unlike traditional component libraries that you install as a dependency, shadcn/ui gives you full ownership of your components by copying the source code directly into your project.
Why shadcn/ui?
Traditional component libraries like MUI or Chakra UI come as black-box packages. You're constrained by their API, their styling approach, and their release cycles.
shadcn/ui flips this model:
- Components are copied into your codebase — you own them
- Built on Radix UI primitives for accessibility
- Styled with Tailwind CSS — easy to customise
- No runtime overhead — just components
shadcn/ui isn't a library you install — it's a collection of components you copy. This means no version lock-in and complete freedom to modify.
Getting Started
First, ensure you have a Next.js project with Tailwind CSS set up, then run:
npx shadcn@latest initThis sets up your components.json configuration and installs the required dependencies.
Adding Components
Add any component with a single command:
npx shadcn@latest add button card badge inputThis copies the component files directly into your components/ui/ directory.
Building a Feature Card
Here's a practical example combining the Button and Card components:
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
interface FeatureCardProps {
title: string
description: string
badge?: string
onLearnMore: () => void
}
export function FeatureCard({
title,
description,
badge,
onLearnMore,
}: FeatureCardProps) {
return (
<Card className="w-full max-w-sm">
<CardHeader>
{badge && (
<Badge variant="secondary" className="w-fit mb-2">
{badge}
</Badge>
)}
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Click below to learn more about this feature and how it can
improve your workflow.
</p>
</CardContent>
<CardFooter className="flex gap-2">
<Button onClick={onLearnMore}>Learn more</Button>
<Button variant="outline">Dismiss</Button>
</CardFooter>
</Card>
)
}Customising the Theme
shadcn/ui uses CSS variables for theming. You can customise every colour in your globals.css:
:root {
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--radius: 0.5rem;
}Dark Mode
Dark mode works out of the box with next-themes. Shadcn's components automatically pick up your dark mode CSS variables.
import { ThemeProvider } from 'next-themes'
export default function Layout({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}shadcn/ui is the component system I reach for on every new project. The combination of Radix accessibility, Tailwind customisability, and full code ownership is hard to beat.