shadcn/ui Examples: Composed Blocks You Can Copy

shadcn/ui is a set of React components you copy into your project with its CLI rather than install as a dependency. They are built on Radix primitives and styled with Tailwind. The value comes from combining them: below are eight everyday blocks, such as a sign-in card, pricing switch and delete confirmation, each live with code.

What you get
PartComes from
Behaviour and accessibilityRadix primitives (focus, keyboard, ARIA)
StylingTailwind classes and CSS variables in your project
Source codeCopied into components/ui; you can edit it

Updated

The previews run on this page using our own copies of the same components, themed with our colours, so buttons and corners look a little different from the stock theme. The code is plain shadcn/ui and works after the add command shown first.

Building a form or a data table? See shadcn form and table.

Setup

Which components each block uses
BlockComponents
Sign-in cardcard, input, label, button
Stats rowcard, badge
Pricing cardcard, switch, label, button
Settings listcard, label, switch
Account tabstabs
Confirm deletedialog, button
Empty statecard, button
Team listavatar, badge

Add shadcn/ui to a project

shadcn/ui is not a package you import from npm. The CLI copies each component's source into your project, so you own and can edit it.

npx shadcn@latest init
npx shadcn@latest add button card input label switch tabs dialog badge avatar

Eight composed blocks

Sign-in card

Card, Label, Input and Button. Every input has a visible label tied to it with htmlFor; the secondary link sits beside the label it relates to.

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export function SignInCard() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Sign in</CardTitle>
        <CardDescription>Use the email you booked with.</CardDescription>
      </CardHeader>
      <CardContent className="grid gap-4">
        <div className="grid gap-2">
          <Label htmlFor="email">Email</Label>
          <Input id="email" type="email" autoComplete="email" placeholder="[email protected]" />
        </div>
        <div className="grid gap-2">
          <div className="flex items-center justify-between">
            <Label htmlFor="password">Password</Label>
            <a href="/forgot" className="text-sm underline">Forgot it?</a>
          </div>
          <Input id="password" type="password" autoComplete="current-password" />
        </div>
      </CardContent>
      <CardFooter><Button className="w-full">Sign in</Button></CardFooter>
    </Card>
  );
}

Stats row

Three cards in a responsive grid. tabular-nums keeps the figures from jumping when they change.

const stats = [
  { label: "Bookings this week", value: "48", change: "+12%" },
  { label: "New customers", value: "17", change: "+4" },
  { label: "No-shows", value: "2", change: "-3" },
];

<div className="grid gap-4 sm:grid-cols-3">
  {stats.map((s) => (
    <Card key={s.label}>
      <CardHeader className="pb-2">
        <CardDescription>{s.label}</CardDescription>
        <CardTitle className="text-3xl tabular-nums">{s.value}</CardTitle>
      </CardHeader>
      <CardContent>
        <Badge variant="secondary">{s.change}</Badge>
      </CardContent>
    </Card>
  ))}
</div>

Pricing card with a plan switch

A Switch flips monthly and yearly prices. The label is clickable and the price change is announced because it sits in an aria-live region.

const [yearly, setYearly] = useState(false);

<Card className="max-w-xs">
  <CardHeader>
    <div className="flex items-center gap-2">
      <Switch id="billing" checked={yearly} onCheckedChange={setYearly} />
      <Label htmlFor="billing">Pay yearly</Label>
    </div>
    <CardTitle>Studio</CardTitle>
    <p aria-live="polite" className="text-3xl font-semibold">
      {yearly ? "$190" : "$19"}<span className="text-sm font-normal">/{yearly ? "year" : "month"}</span>
    </p>
  </CardHeader>
  <CardContent>
    <ul className="space-y-1 text-sm">
      <li>Online booking</li><li>Reminders by email</li><li>Your own domain</li>
    </ul>
  </CardContent>
  <CardFooter><Button className="w-full">Choose Studio</Button></CardFooter>
</Card>

Settings list with switches

Each row pairs a Label and description with a Switch. Using Label htmlFor makes the whole text clickable, not just the small toggle.

const prefs = [
  { id: "reminders", title: "Booking reminders", text: "Email customers a day before." },
  { id: "reviews", title: "Review requests", text: "Ask for a review after each visit." },
];

<Card>
  <CardHeader><CardTitle>Notifications</CardTitle></CardHeader>
  <CardContent className="divide-y">
    {prefs.map((p) => (
      <div key={p.id} className="flex items-center justify-between gap-4 py-3">
        <div>
          <Label htmlFor={p.id}>{p.title}</Label>
          <p className="text-sm text-muted-foreground">{p.text}</p>
        </div>
        <Switch id={p.id} defaultChecked />
      </div>
    ))}
  </CardContent>
</Card>

Tabs for account sections

shadcn Tabs are built on Radix, so arrow keys, Home and End already work and the ARIA roles are set for you.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

<Tabs defaultValue="profile" className="w-full max-w-md">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="billing">Billing</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">Your name, photo and bio.</TabsContent>
  <TabsContent value="billing">Your plan and invoices.</TabsContent>
</Tabs>

Confirm before deleting

Dialog handles focus trapping, Escape and returning focus to the trigger. Make the destructive button say exactly what will happen.

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

<Dialog>
  <DialogTrigger asChild><Button variant="destructive">Delete page</Button></DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Delete "About us"?</DialogTitle>
      <DialogDescription>This removes the page and its menu link. You cannot undo it.</DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose asChild><Button variant="outline">Keep page</Button></DialogClose>
      <Button variant="destructive" onClick={deletePage}>Delete page</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Empty state

A friendly empty state says what will appear here and offers the one action that fills it.

<Card className="border-dashed text-center">
  <CardHeader>
    <CardTitle>No bookings yet</CardTitle>
    <CardDescription>Share your booking link and new bookings will show up here.</CardDescription>
  </CardHeader>
  <CardFooter className="justify-center gap-2">
    <Button>Copy booking link</Button>
    <Button variant="ghost">Add one by hand</Button>
  </CardFooter>
</Card>

Team list with avatars

Avatar shows the photo and falls back to initials when it is missing or still loading. The image needs alt text naming the person.

const team = [
  { name: "Maya Chen", role: "Owner", img: "/team/maya.jpg" },
  { name: "Tom Reid", role: "Stylist", img: "" },
];

<ul className="space-y-3">
  {team.map((m) => (
    <li key={m.name} className="flex items-center gap-3">
      <Avatar>
        <AvatarImage src={m.img} alt={m.name} />
        <AvatarFallback>{m.name.split(" ").map((w) => w[0]).join("")}</AvatarFallback>
      </Avatar>
      <div>
        <p className="font-medium">{m.name}</p>
        <p className="text-sm text-muted-foreground">{m.role}</p>
      </div>
      <Badge variant="outline" className="ml-auto">{m.role}</Badge>
    </li>
  ))}
</ul>

Forms and tables

Forms and data tables are the two places shadcn/ui hands most of the work to other libraries: React Hook Form with Zod for forms, and TanStack Table for tables. Our shadcn form and table guide covers both with live demos.

Common mistakes

Mixing Radix packages from a different version than the one the copied component expects, which causes odd type errors. Add components with the CLI so it installs matching dependencies.

Overriding styles with long className strings on every usage. Add a variant to the component once instead.

How it works

  1. Run npx shadcn@latest init in a React project that uses Tailwind.
  2. Add the components a block needs with npx shadcn@latest add.
  3. Paste the block's code into your page or a new component file.
  4. Change the text and data; restyle through your theme variables, not each class.

Tips

Frequently asked questions

Is shadcn/ui free to use commercially?

Yes. shadcn/ui is open source under the MIT licence, so you can use and change the components in commercial projects.

Is shadcn/ui a component library?

Not in the usual sense. There is no package of components to update; the CLI copies the source into your project and you own it from then on. That makes changes easy but means updates are manual.

Does shadcn/ui work without Next.js?

Yes. It works in any React setup with Tailwind, including Vite, React Router and Astro with React. The CLI asks which framework you use during init.

Where are shadcn/ui landing page templates?

The official site has a set of blocks, and many community templates build full pages from them. For a quick start, compose sections like the ones here into a page, or use a website builder for the marketing site and keep shadcn/ui for the app.

Related: shadcn form and table, React Hook Form examples, UI components, React icons, All free tools.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap