PixelDosa UI

Card

A bordered content container with composable header, title, description, action, content, footer and image regions.

Content card
Plated dosa set
Serves two, ready in 12 minutes.

Crisp rice-and-lentil crepe, coconut chutney, and sambar on the side.

Listing card
A wooden cabin overlooking a mountain valley at dusk
Ridgeline cabin
Alpine views, sleeps four, 8 nights available.
$210 / night
Pricing pair
Free
For hobbyists
$0
  • 3 users
  • 10 downloads / month
  • Roster files
Pro
For agencies
$40
  • 3 users
  • Unlimited downloads
  • Fully editable files
  • Custom assets
  • 200+ custom icons

Installation

bash
npx shadcn@latest add @pixeldosa/card

The example above also uses Button. To copy it as-is, install everything it needs:

bash
npx shadcn@latest add @pixeldosa/card @pixeldosa/button

Requires `cn` at `@/lib/utils`, which `npx shadcn init` creates. CardTitle and CardDescription render `div` elements, not heading tags, matching the wider shadcn/ui convention — supply your own heading element as content if the card needs to participate in the page's document outline. In `orientation="horizontal"`, Card does not auto-arrange its children into an image column and a content column — wrap the header/content/footer parts in your own flex column alongside CardImage.

Usage

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

export function OrderSummary() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Plated dosa set</CardTitle>
        <CardDescription>Serves two, ready in 12 minutes.</CardDescription>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground">
          Crisp rice-and-lentil crepe, coconut chutney, and sambar on the side.
        </p>
      </CardContent>
      <CardFooter className="border-t">
        <Button className="w-full">Add to order</Button>
      </CardFooter>
    </Card>
  );
}

CardAction places a trailing element — a menu trigger, a badge — in the header's second column, aligned to the top-right regardless of how much title or description text wraps:

<CardHeader>
  <CardTitle>Plated dosa set</CardTitle>
  <CardDescription>Serves two, ready in 12 minutes.</CardDescription>
  <CardAction>
    <Button variant="ghost" size="icon" aria-label="More options">
      <MoreIcon />
    </Button>
  </CardAction>
</CardHeader>

CardImage adds a full-bleed image region. In the default vertical orientation it sits first and renders as a top banner; give Card orientation="horizontal" and it becomes a fixed-width, left-rounded side panel instead — wrap the remaining parts in your own flex column so they sit beside it:

<Card orientation="horizontal" className="max-w-lg">
  <CardImage src="/cabin.jpg" alt="A wooden cabin overlooking a mountain valley" />
  <div className="flex flex-1 flex-col justify-between">
    <CardHeader>
      <CardTitle>Ridgeline cabin</CardTitle>
      <CardDescription>Alpine views, sleeps four.</CardDescription>
    </CardHeader>
    <CardFooter>
      <Button size="sm">Reserve now</Button>
    </CardFooter>
  </div>
</Card>

A pricing card needs none of the above — it's the same Card / CardHeader / CardTitle / CardDescription / CardContent / CardFooter parts from the first example, just with a feature list and a price as content. Highlight a tier with a plain className override (border-2 border-primary) rather than a prop, since "highlighted" is a one-off treatment, not a structural variant:

<Card className="border-2 border-primary">
  <CardHeader>
    <CardTitle>Pro</CardTitle>
    <CardDescription>For agencies</CardDescription>
  </CardHeader>
  <CardContent className="flex flex-col gap-4">
    <span className="text-3xl font-semibold">$40</span>
    <ul className="flex flex-col gap-2 text-sm text-muted-foreground">
      <li>Unlimited downloads</li>
      <li>Fully editable files</li>
    </ul>
  </CardContent>
  <CardFooter>
    <Button className="w-full">Subscribe</Button>
  </CardFooter>
</Card>

Features

  • Eight composable parts rather than a props-driven mega-component, so a card can be a summary, a listing, or a pricing tier without any of them being a special case.
  • A self-arranging header: CardHeader grows a second column automatically when a CardAction is present, keeping the trailing control top-aligned however much the title and description wrap.
  • Two orientations: vertical renders CardImage as a top banner, horizontal as a fixed-width, left-rounded side panel. The showcase variant removes the default vertical padding and gap for preview-led compositions.
  • No invented interaction semantics — every part is a plain div, so making a card actionable stays an explicit decision (see Accessibility).

Variants

The default vertical composition — header, content, and a separated footer:

Plated dosa set
Serves two, ready in 12 minutes.

Crisp rice-and-lentil crepe, coconut chutney, and sambar on the side.

Showcase composition

Use variant="showcase" when a preview or image is the card's primary content. It removes the default vertical padding and gap so the media can sit flush with the card edge; compose the remaining content explicitly:

<Card variant="showcase">
  <img src="/preview.png" alt="Dashboard preview" className="aspect-video w-full object-cover" />
  <CardFooter className="justify-between gap-3 pt-4">
    <div>
      <CardTitle>Analytics workspace</CardTitle>
      <CardDescription>Explore product metrics and trends.</CardDescription>
    </div>
    <Button variant="outline" size="sm">Open preview</Button>
  </CardFooter>
</Card>

With a header action

CardAction takes the header's second column, so the trigger stays pinned top-right no matter how long the title runs.

Weekly digest
Sent every Monday at 9am.

Delivered to 1,284 subscribers.

Highlighted

A highlighted tier is a className override, not a prop — "highlighted" is a one-off treatment, not a structural variant worth encoding in the API.

Pro
For agencies
$40

For the horizontal orientation and the full listing/pricing compositions, see the examples at the top of this page.

Props

Every part forwards className and all native props of the element it renders.

PartRendersNotes
CarddivThe bordered container. bg-card / text-card-foreground, 1px border-border, rounded-xl. Takes orientation="vertical" | "horizontal" (default vertical) and variant="default" | "showcase" (default default). showcase removes vertical padding and gaps for preview-led layouts.
CardHeaderdivGrid layout; adds a second column automatically when a CardAction is present.
CardTitledivNot a heading element — nest a real heading tag as its child if the card is part of the page outline.
CardDescriptiondivMuted, small text.
CardActiondivSelf-aligns to the header's top-right.
CardContentdivHorizontal padding only; pairs with Card's vertical py-6.
CardFooterdivAdd the border-t class yourself when the footer should be visually separated.
CardImageimgFull-bleed banner (vertical) or side panel (horizontal) — see above. Requires alt.

Accessibility

  • All parts are plain divs — Card has no interaction semantics of its own, so there is nothing for Radix to manage here.
  • CardTitle deliberately does not render a heading element, matching the wider shadcn/ui convention. If the card's title should appear in the page's heading outline, render a heading element as its child rather than relying on CardTitle itself for that.
  • Interactive cards (a whole card acting as a link or button) are not this component's job — wrap the actionable content in a Button asChild or an anchor instead of adding onClick to Card directly, so keyboard and screen-reader semantics stay correct.
  • CardImage has no default alt — supply one that describes the image's content. If the image is purely decorative (rare for a listing-style card), pass alt="" explicitly rather than omitting the prop.

why it's built this way

Engineering Notes

Exposed as eight composable parts rather than a single component with header/footer/title/image props, so a Block composing Card can rearrange or omit regions without fighting a monolithic API. CVA expresses two purposeful axes: orientation controls CardImage's banner-versus-side-panel geometry, while the showcase variant removes default vertical padding and gaps for preview-led catalogue cards. A pricing card still needs no special Card capability — it composes the existing parts and its own content. Depth uses border-border rather than a shadow, matching the system's dark-mode-first 'borders over shadows' decision documented in the design tokens.

how it moves, and when it doesn't

Motion Notes

None. Card is compositional infrastructure with no interaction state of its own — motion belongs on components built on top of it (a future SpotlightCard or an interactive card treatment), not baked into the base primitive, per the pillar rule that motion must earn its place.