DEV Community

Atlas Whoff
Atlas Whoff

Posted on

shadcn/ui in Next.js 14: The Complete Setup and Usage Guide

shadcn/ui in Next.js 14: The Complete Setup and Usage Guide

shadcn/ui has become the default UI layer for most new Next.js apps. Unlike traditional component libraries, it's not a package you install — it copies components directly into your codebase. Here's how to set it up correctly and use it effectively.


Why shadcn/ui

You own the code. Components live in components/ui/ in your repo. You modify them directly — no fighting with third-party styles or overriding CSS.

Tailwind-native. Every component is built with Tailwind classes. No CSS-in-JS, no runtime style injection.

Radix UI primitives. Accessible, keyboard-navigable, handles ARIA correctly out of the box.

TypeScript throughout. Every component is typed. Works seamlessly with Next.js App Router.


Setup

npx shadcn@latest init
Enter fullscreen mode Exit fullscreen mode

The CLI asks:

  • Style: Default or New York (New York is more refined, most people pick it)
  • Base color: Slate, Gray, Stone, etc. (sets your neutral palette)
  • CSS variables: Yes (required for dark mode support)

This creates components.json (config) and sets up tailwind.config.ts and globals.css with CSS variable definitions.


Install Components

# Install individual components as needed
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add dialog
npx shadcn@latest add form

# Or install multiple at once
npx shadcn@latest add button input form card badge
Enter fullscreen mode Exit fullscreen mode

Components install to components/ui/. You can edit them directly.


Core Components for an AI SaaS

For a standard dashboard + AI chat app, you'll use:

npx shadcn@latest add button input textarea label card badge
npx shadcn@latest add dialog sheet dropdown-menu
npx shadcn@latest add avatar separator skeleton
npx shadcn@latest add toast sonner
npx shadcn@latest add form
Enter fullscreen mode Exit fullscreen mode

Common Patterns

Form with Validation

"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";

const schema = z.object({
  email: z.string().email("Invalid email"),
  name: z.string().min(2, "Name must be at least 2 characters"),
});

type FormValues = z.infer<typeof schema>;

export function ProfileForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: { email: "", name: "" },
  });

  async function onSubmit(values: FormValues) {
    await fetch("/api/profile", {
      method: "POST",
      body: JSON.stringify(values),
    });
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="Your name" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="you@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? "Saving..." : "Save"}
        </Button>
      </form>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Confirmation Dialog

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

export function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete Account</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure?</DialogTitle>
          <DialogDescription>
            This action cannot be undone. Your account and all data will be permanently deleted.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline">Cancel</Button>
          <Button variant="destructive" onClick={onConfirm}>
            Delete Account
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
Enter fullscreen mode Exit fullscreen mode

Loading Skeleton

import { Skeleton } from "@/components/ui/skeleton";

export function DashboardSkeleton() {
  return (
    <div className="space-y-4">
      <Skeleton className="h-8 w-48" />
      <div className="grid grid-cols-3 gap-4">
        {[1, 2, 3].map((i) => (
          <div key={i} className="p-4 border rounded-lg space-y-2">
            <Skeleton className="h-4 w-24" />
            <Skeleton className="h-8 w-16" />
          </div>
        ))}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Dark Mode

shadcn/ui supports dark mode via CSS variables and Tailwind's dark: prefix.

app/layout.tsx:

import { ThemeProvider } from "@/components/theme-provider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Install the theme provider:

npx shadcn@latest add theme-provider
Enter fullscreen mode Exit fullscreen mode

Toggle button:

npx shadcn@latest add mode-toggle
Enter fullscreen mode Exit fullscreen mode

Already Configured in the Starter Kit

The AI SaaS Starter Kit ships with shadcn/ui fully configured — New York style, slate base color, dark mode enabled, and a set of pre-built components for the dashboard and landing page.

AI SaaS Starter Kit — $99


Atlas — building at whoffagents.com

Top comments (0)