Building Beautiful UIs with shadcn/ui and Tailwind CSS

By Mike Johnson
10 min read
Building Beautiful UIs with shadcn/ui and Tailwind CSS

Building Beautiful UIs with shadcn/ui and Tailwind CSS

Creating beautiful, accessible, and performant user interfaces has never been easier. In this guide, we'll explore how to use shadcn/ui with Tailwind CSS to build production-ready components.

What is shadcn/ui?

shadcn/ui is not a component library in the traditional sense. Instead, it's a collection of re-usable components that you can copy and paste into your apps.

💡NOTE

shadcn/ui components are built using Radix UI and styled with Tailwind CSS. You own the code!

Why shadcn/ui?

Advantages

  1. Full Control - You own the code, modify as needed
  2. Accessible - Built on Radix UI primitives
  3. Customizable - Easy to theme and style
  4. No Dependencies - Copy what you need
  5. Type-Safe - Full TypeScript support

Getting Started

Installation

First, initialize shadcn/ui in your project:

BASH
npx shadcn@latest init

This will set up:

  • Tailwind CSS configuration
  • Component directory structure
  • Utility functions
  • CSS variables for theming

Adding Components

Add components as you need them:

BASH
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog

Component Examples

Button Variants

shadcn/ui provides multiple button variants:

JSX
import { Button } from '@/components/ui/button';
 
function ButtonExamples() {
  return (
    <div className="flex gap-4">
      <Button variant="default">Default</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
    </div>
  );
}

Card Component

Cards are perfect for content containers:

JSX
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
 
function CardExample() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Card Title</CardTitle>
        <CardDescription>Card description goes here</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Your content here</p>
      </CardContent>
    </Card>
  );
}

Dialog (Modal)

Create beautiful modals with ease:

JSX
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
 
function DialogExample() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog Title</DialogTitle>
          <DialogDescription>
            Dialog description and content goes here
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
}

Theming with CSS Variables

shadcn/ui uses CSS variables for theming:

CSS
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  /* ... more variables */
}
 
.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... more variables */
}

Dark Mode Implementation

Implement dark mode easily:

JSX
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
 
function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <Button
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
      variant="outline"
    >
      Toggle Theme
    </Button>
  );
}
SUCCESS

Dark mode is fully supported out of the box with proper color contrast!

Customizing Components

Modifying Styles

Since you own the code, customization is straightforward:

JSX
// 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 your custom variant
        custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
      },
    },
  }
);

Creating Custom Components

Build on top of existing components:

JSX
import { Button } from '@/components/ui/button';
import { Icon } from '@iconify/react';
 
function IconButton({ icon, children, ...props }) {
  return (
    <Button {...props}>
      <Icon icon={icon} className="mr-2 h-4 w-4" />
      {children}
    </Button>
  );
}

Form Components

Input with Label

JSX
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
 
function FormExample() {
  return (
    <div className="space-y-2">
      <Label htmlFor="email">Email</Label>
      <Input 
        id="email" 
        type="email" 
        placeholder="you@example.com" 
      />
    </div>
  );
}

Select Component

JSX
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
 
function SelectExample() {
  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select option" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="option1">Option 1</SelectItem>
        <SelectItem value="option2">Option 2</SelectItem>
        <SelectItem value="option3">Option 3</SelectItem>
      </SelectContent>
    </Select>
  );
}

Responsive Design

Use Tailwind's responsive utilities:

JSX
<Card className="w-full md:w-1/2 lg:w-1/3">
  <CardContent className="p-4 md:p-6">
    Responsive card
  </CardContent>
</Card>

Accessibility Features

shadcn/ui components are accessible by default:

  • Keyboard navigation
  • Screen reader support
  • Focus management
  • ARIA attributes
💡TIP

Always test your components with keyboard navigation and screen readers!

Best Practices

  1. Use semantic HTML - Proper element selection
  2. Follow naming conventions - Consistent component names
  3. Keep components small - Single responsibility
  4. Document custom components - Help your team
  5. Test accessibility - Use automated tools

Real-World Example: Dashboard Card

JSX
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Icon } from '@iconify/react';
 
function DashboardCard({ title, value, icon, trend }) {
  return (
    <Card>
      <CardHeader className="flex flex-row items-center justify-between pb-2">
        <CardTitle className="text-sm font-medium">
          {title}
        </CardTitle>
        <Icon icon={icon} className="h-4 w-4 text-muted-foreground" />
      </CardHeader>
      <CardContent>
        <div className="text-2xl font-bold">{value}</div>
        <p className="text-xs text-muted-foreground">
          {trend > 0 ? '+' : ''}{trend}% from last month
        </p>
      </CardContent>
    </Card>
  );
}

Conclusion

shadcn/ui combined with Tailwind CSS provides a powerful toolkit for building modern user interfaces. The flexibility and control you get by owning the code makes it perfect for production applications.

Resources

Start building beautiful UIs today! 🎨

About the Author

Written by Mike Johnson