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.

Afzal ZubairNovember 15, 20253 min read
Building Beautiful UIs with shadcn/ui

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 init

This 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 input

This 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.

Related Posts