Starting Point
v0.25.0
Start typing to search...
Go to Page
escClose

Card

The card groups related content on one surface, like a login form, a stat, or a settings section.

<div class="card">
  <div class="card-header">
    <h3 class="card-title">Login to your account</h3>
    <p class="card-description">
      Enter your email below to login to your account
    </p>
    <div class="card-action">
      <button class="btn btn-link">Sign Up</button>
    </div>
  </div>
  <div class="card-content">
    <form class="flex flex-col gap-6">
      <div class="field">
        <label class="label" for="card-demo-email">Email</label>
        <input
          class="input"
          id="card-demo-email"
          type="email"
          placeholder="[email protected]"
          required
        />
      </div>
      <div class="field">
        <div class="flex w-full items-center">
          <label class="label" for="card-demo-password">Password</label>
          <a
            href="#"
            class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
          >
            Forgot your password?
          </a>
        </div>
        <input class="input" id="card-demo-password" type="password" required />
      </div>
    </form>
  </div>
  <div class="card-footer flex-col gap-2">
    <button type="submit" class="btn w-full">Login</button>
    <button class="btn btn-outline w-full">Login with Google</button>
  </div>
</div>

Usage

Add the card class to the surface and compose the sections inside.

ClassDescription
cardAdd to the surface; spacing derives from --card-spacing
card-smAdd to card for compact spacing and a smaller title
card-headerAdd to the top section holding title, description, and action
card-titleAdd to a heading element
card-descriptionAdd to a paragraph with supporting text
card-actionAdd to an element in the header to pin it to the top-right corner
card-contentAdd to the main section
card-footerAdd to the bottom section, a horizontal row by default
<div class="card">
  <div class="card-header">
    <h3 class="card-title">Card Title</h3>
    <p class="card-description">Card Description</p>
    <div class="card-action">Card Action</div>
  </div>
  <div class="card-content">Card Content</div>
  <div class="card-footer">Card Footer</div>
</div>

Examples

Size

Add card-sm for a more compact card: the spacing tightens and the title drops a step.

<div class="card card-sm mx-auto w-full max-w-xs">
  <div class="card-header">
    <h3 class="card-title">Scheduled reports</h3>
    <p class="card-description">Weekly snapshots. No manual exports.</p>
  </div>
  <div class="card-content">
    <ul class="grid gap-2 py-2 text-sm">
      <li class="flex gap-2">
        <svg class="mt-0.5 size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
        <span>Choose a schedule (daily, or weekly).</span>
      </li>
      <li class="flex gap-2">
        <svg class="mt-0.5 size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
        <span>Send to channels or specific teammates.</span>
      </li>
      <li class="flex gap-2">
        <svg class="mt-0.5 size-4 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
        <span>Include charts, tables, and key metrics.</span>
      </li>
    </ul>
  </div>
  <div class="card-footer flex-col gap-2">
    <button class="btn btn-sm w-full">Set up scheduled reports</button>
    <button class="btn btn-outline btn-sm w-full">See what's new</button>
  </div>
</div>

Spacing

Set --card-spacing to control the section gaps and edge insets in one place.

<div class="btn-group mx-auto" role="group" aria-label="Card spacing">
  <button
    class="btn btn-outline btn-sm aria-pressed:bg-accent aria-pressed:text-accent-foreground"
    data-spacing="4"
    aria-pressed="false"
  >
    16px
  </button>
  <button
    class="btn btn-outline btn-sm aria-pressed:bg-accent aria-pressed:text-accent-foreground"
    data-spacing="5"
    aria-pressed="false"
  >
    20px
  </button>
  <button
    class="btn btn-outline btn-sm aria-pressed:bg-accent aria-pressed:text-accent-foreground"
    data-spacing="6"
    aria-pressed="true"
  >
    24px
  </button>
  <button
    class="btn btn-outline btn-sm aria-pressed:bg-accent aria-pressed:text-accent-foreground"
    data-spacing="8"
    aria-pressed="false"
  >
    32px
  </button>
</div>
 
<div class="card" id="card-spacing-demo">
  <div class="card-header">
    <h3 class="card-title">Login to your account</h3>
    <p class="card-description">
      Enter your email below to login to your account
    </p>
    <div class="card-action">
      <button class="btn btn-link">Sign Up</button>
    </div>
  </div>
  <div class="card-content">
    <form class="flex flex-col gap-6">
      <div class="field">
        <label class="label" for="card-spacing-email">Email</label>
        <input
          class="input"
          id="card-spacing-email"
          type="email"
          placeholder="[email protected]"
          required
        />
      </div>
      <div class="field">
        <div class="flex w-full items-center">
          <label class="label" for="card-spacing-password">Password</label>
          <a
            href="#"
            class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
          >
            Forgot your password?
          </a>
        </div>
        <input
          class="input"
          id="card-spacing-password"
          type="password"
          required
        />
      </div>
    </form>
  </div>
  <div class="card-footer flex-col gap-2">
    <button type="submit" class="btn w-full">Login</button>
    <button class="btn btn-outline w-full">Login with Google</button>
  </div>
</div>
 
<script>
  const card = document.getElementById("card-spacing-demo");
const buttons = document.querySelectorAll("[data-spacing]");
buttons.forEach((button) => {
  button.addEventListener("click", () => {
    buttons.forEach((b) =>
      b.setAttribute("aria-pressed", String(b === button)));
    card.style.setProperty(
      "--card-spacing",
      `calc(var(--spacing) * ${button.dataset.spacing})`,
    );
  });
});
</script>

Edge to edge

Use negative margins with -mx-(--card-spacing) to run content to the card edges while keeping it aligned with the inset.

<div class="card">
  <div class="card-header">
    <h3 class="card-title">Terms of Service</h3>
    <p class="card-description">
      Review the terms before accepting the agreement.
    </p>
  </div>
  <div class="card-content">
    <div
      class="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-y bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed"
    >
      <p>
        These terms govern your use of the workspace, including access to shared
        documents, project files, and collaboration tools.
      </p>
      <p>
        You are responsible for the content you upload and for ensuring that
        your team has the appropriate permissions to view or edit it.
      </p>
      <p>
        We may update features or limits as the service evolves. When those
        changes materially affect your workflow, we will notify your workspace
        administrators.
      </p>
      <p>
        By continuing, you agree to keep your account credentials secure and to
        follow your organization's acceptable use policies.
      </p>
    </div>
  </div>
  <div class="card-footer justify-end gap-2">
    <button class="btn btn-outline">Decline</button>
    <button class="btn">Accept</button>
  </div>
</div>

Image

Add an image before the card header to create a card with an image.

<div class="card relative pt-0">
  <div class="absolute inset-0 z-30 aspect-video bg-black/35"></div>
  <img
    src="https://avatar.vercel.sh/shadcn1"
    alt="Event cover"
    class="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40"
  />
  <div class="card-header">
    <div class="card-action">
      <span class="badge badge-secondary">Featured</span>
    </div>
    <h3 class="card-title">Design systems meetup</h3>
    <p class="card-description">
      A practical talk on component APIs, accessibility, and shipping faster.
    </p>
  </div>
  <div class="card-footer">
    <button class="btn w-full">View Event</button>
  </div>
</div>

CSS variables

The card's spacing rhythm is one component-local variable: the card derives its vertical padding and section gap from it, and each section its horizontal inset. Override it per instance with an arbitrary property, or for the whole app with a plain CSS rule.

VariableDefaultDescription
--card-spacing1.5rem (1rem with card-sm)Padding and gap rhythm of the card and its sections
<div class="card [--card-spacing:--spacing(8)]"></div>