Compose recipes, not div soup

Build from Tabs, Table, Sheet and Card, not stacked bordered divs.

From shadcn (Vercel plugin): Composition recipes and Anti-patterns.

Same settings, two structures

Edit and save both, then outline the boxes.

Stacked bordered divsBad

Settings

Profile

Notifications

Tabs + Card per groupGood

Profile

How teammates see you.


<Tabs defaultValue="profile">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="notifications">Notifications</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">
    <Card>
      <CardHeader>
        <CardTitle>Profile</CardTitle>
        <CardDescription>How teammates see you.</CardDescription>
      </CardHeader>
      <CardContent className="space-y-4">{/* Form fields */}</CardContent>
      <Separator />
      <CardFooter className="justify-end">
        <Button disabled={!form.formState.isDirty}>Save</Button>
      </CardFooter>
    </Card>
  </TabsContent>
</Tabs>

Pick a use case, get a recipe

Type what you’re building, or pick a use case.

Use case
Div per regionBad
The recipeGood

Card in card in card

Drag to add grouping levels.

One card per levelBad
Levels in one headerGood
<Card>
  <CardHeader>
    <CardDescription>Workspace / Billing / Plan</CardDescription>
    <CardTitle>Usage</CardTitle>
  </CardHeader>
  <Separator />
  <CardContent>{/* seats */}</CardContent>
</Card>

Why it matters, pros and cons

Why it matters

A border says “separate”. Around everything, it says nothing.

Pros

  • One Card and a Separator carry grouping.
  • Overlays bring focus and dismiss for free.
  • Content keeps its width at any depth.

Cons

  • Dense data tools may want only a Table.
  • A nested card is fine for a different object.
  • Heavy primitives like Sheet and Command cost bundle size.