MODULE 05
Slide of 38
MODULE 05 • COMPONENT ARCHITECTURE

Shadcn UI
Accessible Component Architecture

“Beautifully designed components that you copy and paste into your apps”

Learn how to architect accessible, customizable React component systems built on Radix UI primitives and Tailwind CSS.
or press Space / →
bash — add components
$pnpm dlx shadcn@latest add button card dialog
Installing dependencies: @radix-ui/react-slot, @radix-ui/react-dialog...
Created components/ui/button.tsx
Created components/ui/card.tsx
Created components/ui/dialog.tsx

Success! 3 components added to your codebase.
OVERVIEW

What Makes Shadcn UI Different?

Why it is taking over the React and Next.js ecosystem

Traditional Component Libraries (MUI/Chakra)

You install a massive npm package. Overriding styles is painful with complex CSS-in-JS wrappers. Upgrades break your custom designs.

You are locked into their opinions and cannot easily modify internal component code.

The Shadcn UI Philosophy

It is NOT an npm dependency. The source code lives directly inside your src/components/ui/ directory. You own and customize every line.

Built on unstyled accessible Radix UI primitives and styled with standard Tailwind utility classes.
ARCHITECTURE

The Three Pillars of Shadcn UI

How modern web building blocks unite

01 Radix UI Primitives

Handles accessibility, keyboard navigation (Tab/Arrow keys), ARIA labels, focus trapping, and screen reader announcements.

02 Tailwind CSS

Provides utility styling, responsive breakpoints, dark mode variants, and smooth hover/active transitions.

03 Lucide Icons

Clean, lightweight, tree-shakeable SVG icons that integrate seamlessly with typography and buttons.

SETUP

Initializing Shadcn UI

Configuring components.json in your project

bash — init shadcn
$pnpm dlx shadcn@latest init
Which style would you like to use? › New York
Which color would you like to use as base color? › Zinc
Would you like to use CSS variables for colors? › Yes
Where is your global CSS file? › src/app/globals.css
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils

Success! Project initialized with components.json
UTILITIES

The cn() Utility & Class Merging

Safely combining conditional classes and overriding Tailwind styles

src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
tailwind-merge resolves Tailwind class conflicts intelligently. If a component defines p-4 and the caller passes p-6, p-6 wins cleanly instead of both classes fighting in CSS!
COMPONENTS

Adding Components via CLI

Grabbing exactly what you need without dependency bloat

bash — add components
$pnpm dlx shadcn@latest add button badge card dialog
Installing @radix-ui/react-slot @radix-ui/react-dialog...
Created src/components/ui/button.tsx
Created src/components/ui/badge.tsx
Created src/components/ui/card.tsx
Created src/components/ui/dialog.tsx
Done! You can now import them into any page.
BUTTON

The Button Component & Variants

Understanding class-variance-authority (cva)

Usage Example
import { Button } from '@/components/ui/button';
import { ArrowRight, Trash2 } from 'lucide-react';

export function ButtonShowcase() {
  return (
    <div className="flex flex-wrap items-center gap-3 p-4">
      <Button variant="default">Primary Button</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="destructive" size="sm">
        <Trash2 className="w-4 h-4 mr-2" /> Delete
      </Button>
      <Button variant="default" size="lg">
        Get Started <ArrowRight className="w-4 h-4 ml-2" />
      </Button>
    </div>
  );
}
CARD

Card Component Architecture

Composing headers, content, and actions semantically

components/ProjectCard.tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';

export function ProjectCard() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <div className="flex justify-between items-center">
          <CardTitle>Repository Sync</CardTitle>
          <Badge variant="outline">Active</Badge>
        </div>
        <CardDescription>Last synchronized 2 minutes ago</CardDescription>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground">Branches: main, feature-auth</p>
      </CardContent>
      <CardFooter className="flex justify-between">
        <Button variant="outline">View Details</Button>
        <Button>Sync Now</Button>
      </CardFooter>
    </Card>
  );
}
MODALS

Accessible Dialogs & Overlays

Full accessibility with focus traps and escape key handling

Dialog Example
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';

export function DeleteRepoDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete Project</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you absolutely sure?</DialogTitle>
          <DialogDescription>
            This action cannot be undone. This will permanently delete your repository from the server.
          </DialogDescription>
        </DialogHeader>
        <div className="flex justify-end gap-3 mt-4">
          <Button variant="outline">Cancel</Button>
          <Button variant="destructive">Confirm Delete</Button>
        </div>
      </DialogContent>
    </Dialog>
  );
}
THEMING

Theming & CSS Variables

Effortless dark mode with design tokens

src/app/globals.css (Tokens)
:root {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --card: 0 0% 100%;
  --primary: 240 5.9% 10%;
  --primary-foreground: 0 0% 98%;
  --radius: 0.5rem;
}

.dark {
  --background: 240 10% 3.9%;
  --foreground: 0 0% 98%;
  --card: 240 10% 5%;
  --primary: 0 0% 98%;
  --primary-foreground: 240 5.9% 10%;
}
Because components reference bg-background and text-foreground, toggling the dark class changes the entire app's appearance seamlessly!
COMPOSITION

Putting It Together: Full Widget

Composing multiple Shadcn components into a production UI

DashboardWidget.tsx
export function DashboardWidget() {
  return (
    <Card className="p-4 border-slate-800 bg-slate-900">
      <div className="flex items-center justify-between mb-4">
        <Badge>Production Ready</Badge>
        <span className="text-xs text-slate-400">v2.4.0</span>
      </div>
      <h3 className="font-bold text-white text-lg">Deploy Pipeline</h3>
      <p className="text-sm text-slate-400 mt-1">Zero-config deployment with Git push</p>
      <div className="flex gap-2 mt-4">
        <Button className="w-full">Deploy Now</Button>
      </div>
    </Card>
  );
}

Why This Architecture Wins

Completely accessible out of the box

Code is stored in your repo, not node_modules

Zero runtime performance overhead

Perfectly matches your custom design system

SUMMARY

Curriculum Completed!

You have traversed the complete modern web development stack

01. Git: Local tracking, commits, branches, mental model
02. GitHub: Cloud repositories, remotes, push/pull, pull requests
03. Next.js: App Router, Server Components, SSR, API routes
04. Tailwind CSS: Utility classes, responsive design, dark mode
05. Shadcn UI: Radix primitives, accessible components, clean design
Fullstack Readiness: From your first line of code to production!
Congratulations on completing the entire 5-module curriculum!