Shadcn UI: A Front-End Developer’s Deep Dive into a Modern UI Toolkit

Nima JanbazNima Janbaz
3 min read

As a front-end dev, I’ve been diving into Shadcn UI—a minimalist toolkit that blends power and control. It’s a fresh take on UI libraries, perfect for modern workflows.

The Shadcn Philosophy: Less Is More

Shadcn UI isn’t here to hand you a bloated, opinionated design system. Instead, it’s a "bring-your-own-components" toolkit built on Tailwind CSS and Radix UI primitives. You get pre-styled, accessible components as a starting point—not a finish line. The source code gets copied into your project, meaning you own it entirely. No black-box dependencies, no runtime CSS-in-JS overhead—just lean, maintainable code.

What Sets It Apart?

  • Zero Runtime Styling: Unlike Chakra UI (Emotion) or MUI (JSS), Shadcn leans on Tailwind and CSS variables. No JavaScript bloat parsing styles at runtime.

  • Radix Under the Hood: Built on Radix UI’s headless components, it ensures accessibility and flexibility without dictating your UI.

  • Bundle Size: At ~45kb, it’s a featherweight compared to MUI’s 300kb+ or even Chakra’s 120kb.

  • TypeScript-First: Ships with tight TypeScript integration, which is a godsend for type safety.

Getting Started: Beyond the Basics

Let’s set it up in a Next.js project and push it further than a simple button. I’ll assume you’ve got a basic Next.js app with Tailwind already configured (if not, check the Tailwind docs).

Installation

Initialize Shadcn:

npx shadcn-ui@latest init

This scaffolds a components/ui directory and tweaks your tailwind.config.js to include CSS variables (optional but recommended). Add some components:

npx shadcn-ui@latest add button dropdown-menu dialog

Anatomy of a Component

Here’s what the generated button.tsx looks like (simplified):

import { cva, type VariantProps } from "class-variance-authority";
import { forwardRef } from "react";

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",
        outline: "border border-input hover:bg-accent",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => {
    return (
      <button
        className={buttonVariants({ variant, size, className })}
        ref={ref}
        {...props}
      />
    );
  }
);

Button.displayName = "Button";

export { Button, buttonVariants };

What’s happening here? CVA (Class Variance Authority): A utility for defining variant-based styles in a type-safe way. ForwardRef: Ensures the component can handle refs, which is clutch for focus management or animations. Tailwind-Driven: The base styles and variants use Tailwind classes, keeping things predictable.

Building Something Real

Let’s create a dropdown-triggered dialog—a common UI pattern that shows off Shadcn’s composability.

// app/components/Header.tsx
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";

export default function Header() {
  return (
    <header className="p-4 border-b">
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button variant="outline">Menu</Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          <Dialog>
            <DialogTrigger asChild>
              <DropdownMenuItem onSelect={(e) => e.preventDefault()}>
                Open Settings
              </DropdownMenuItem>
            </DialogTrigger>
            <DialogContent>
              <DialogHeader>
                <DialogTitle>Settings</DialogTitle>
                <DialogDescription>
                  Customize your app preferences here.
                </DialogDescription>
              </DialogHeader>
              <div className="space-y-4">
                <label className="block">
                  Theme
                  <select className="mt-1 block w-full rounded-md border p-2">
                    <option>Light</option>
                    <option>Dark</option>
                  </select>
                </label>
                <Button>Save</Button>
              </div>
            </DialogContent>
          </Dialog>
          <DropdownMenuItem>Profile</DropdownMenuItem>
          <DropdownMenuItem>Logout</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </header>
  );
}

Read More:

https://nimajanbaz.dev/blog/shadcn-ui-a-front-end-developers-deep-dive-into-a-modern-ui-toolkit

0
Subscribe to my newsletter

Read articles from Nima Janbaz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nima Janbaz
Nima Janbaz