All topics
Frontend · Learning hub

Shadcn notes for developers

Master Shadcn with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Shadcn

shadcn/ui Components

shadcn/ui Components Setup & Installation # Init shadcn in project npx shadcn@latest init # Add individual components (copies source to your project) npx shadcn

shadcn/ui Components

Setup & Installation

# Init shadcn in project
npx shadcn@latest init

# Add individual components (copies source to your project)
npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add form input label
npx shadcn@latest add table data-table
npx shadcn@latest add sonner    # toasts
npx shadcn@latest add command   # command palette
npx shadcn@latest add sheet     # side drawer
npx shadcn@latest add dropdown-menu select
npx shadcn@latest add calendar date-picker

# Components live in src/components/ui/ — you own the code, modify freely

Common Patterns

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Dialog, DialogContent, DialogDescription,
  DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from '@/components/ui/dialog';
import {
  Form, FormControl, FormField, FormItem, FormLabel, FormMessage,
} from '@/components/ui/form';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { toast } from 'sonner';

// Dialog
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure?</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline">Cancel</Button>
          <Button variant="destructive" onClick={onConfirm}>Delete</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// Form with react-hook-form + zod + shadcn
const formSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: { email: '', password: '' },
  });

  async function onSubmit(values: z.infer<typeof formSchema>) {
    try {
      await login(values);
      toast.success('Logged in!');
    } catch {
      toast.error('Invalid credentials');
    }
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="alice@example.com" {...field} />
              </FormControl>
              <FormMessage />   {/* shows validation error */}
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" disabled={form.formState.isSubmitting} className="w-full">
          {form.formState.isSubmitting ? 'Logging in...' : 'Log in'}
        </Button>
      </form>
    </Form>
  );
}

cn() Utility & Theming

// cn() — merge Tailwind classes safely (avoids conflicts)
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Usage in custom component
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cn('rounded-lg border bg-card p-6 shadow-sm', className)}
      {...props}
    />
  );
}

// CSS variables for theming (globals.css)
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  --muted: 210 40% 96.1%;
  --border: 214.3 31.8% 91.4%;
  --radius: 0.5rem;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 210 40% 98%;
}

// Button variants reference
<Button variant="default" />       // primary action
<Button variant="secondary" />     // less prominent
<Button variant="destructive" />   // dangerous action
<Button variant="outline" />       // bordered, no fill
<Button variant="ghost" />         // no border, subtle hover
<Button variant="link" />          // looks like anchor
<Button size="sm|md|lg|icon" />

Keep your Shadcn knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever