Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha
API navigation

API

Card

A surface that groups related content.

Installation

pnpm dlx zazz-ui add card
npx zazz-ui add card
yarn dlx zazz-ui add card
bunx zazz-ui add card

Run zazz-ui init first if the project has no zazz/ folder yet.

Examples

Default

<a
  href="#"
  style="
    --display: grid;
    --cursor: pointer;
  "
>
  <div data-ui="card">
    <figure
      style="
        --display: grid;
        --aspect: 3 / 2;
        --rounded: var(--radius-md) var(--radius-md) 0 0;
        --overflow: clip;
      "
    >
      <img
        width="300"
        height="200"
        loading="lazy"
        src="https://images.unsplash.com/photo-1564760055775-d63b17a55c44?auto=format&fit=crop&w=300&h=200&q=60"
        alt="Elephants crossing a dusty riverbed"
        style="
          --size: 100%;
          --opacity: 1;
          --opacity--hover: 0.75;
          --scale: 1;
          --scale--hover: 1.02;
          --transition: var(--default-transition);
          --object-fit: cover;
        "
      />
    </figure>
    <div
      style="
        --display: grid;
        --px: 4;
        --pb: 4;
        --gap: 4;
      "
    >
      <header
        style="
          --display: grid;
          --gap: 2;
        "
      >
        <div
          style="
            --display: flex;
            --items: center;
            --gap: 2;
          "
        >
          <span data-ui="badge" data-badge-variant="muted">Research</span>
        </div>
        <hgroup
          style="
            --display: grid;
            --gap: 2;
          "
        >
          <h3
            style="
              --font-size: var(--font-size-lg);
              --font-family: var(--font-family-heading);
              --leading: var(--leading-lg);
              --tracking: var(--tracking-lg);
            "
          >
            Migration patterns
          </h3>
          <p
            style="
              --font-size: var(--font-size-sm);
              --leading: var(--leading-sm);
              --tracking: var(--tracking-sm);
              --line-clamp: 2;
              --text: var(--color-muted-foreground);
            "
          >
            Satellite collars reveal ancient routes herds still follow each dry season across the
            floodplain.
          </p>
        </hgroup>
      </header>
      <footer
        style="
          --display: grid;
          --justify: start;
          --pt: 4;
          --gap: 4;
          --border-t: 1;
        "
      >
        <div
          style="
            --display: flex;
            --items: center;
            --gap: 2;
          "
        >
          <figure
            data-ui="pile"
            role="img"
            style="
              --display: grid;
              --size: 11;
              --border-color: var(--color-border);
              --border-width: 1px;
              --rounded: var(--radius-full);
              --overflow: clip;
            "
          >
            <img
              width="64"
              height="64"
              loading="lazy"
              src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2&w=64&h=64&q=90"
              alt="John Doe"
              aria-hidden="true"
              style="
                --order: 9999;
                --size: 100%;
                --bg: transparent;
                --object-fit: cover;
              "
            />
            <figcaption
              style="
                --display: flex;
                --items: center;
                --justify: center;
                --order: -9999;
                --size: 100%;
                --font-size: var(--font-size-md);
                --font-weight: strong;
                --leading: var(--leading-md);
                --tracking: var(--tracking-md);
                --text-align: center;
                --text: var(--color-primary-foreground);
                --bg: var(--color-primary);
              "
            >
              <span data-ui="sr-only">John Doe</span>
              <span aria-hidden="true">JD</span>
            </figcaption>
          </figure>
          <div style="--display: flex-col">
            <span
              style="
                --font-size: var(--font-size-sm);
                --font-weight: strong;
                --leading: var(--leading-sm);
                --leading: 1;
                --tracking: var(--tracking-sm);
              "
              >John Doe</span
            >
            <span
              style="
                --font-size: var(--font-size-xs);
                --leading: var(--leading-xs);
                --tracking: var(--tracking-xs);
                --text: var(--color-muted-foreground);
              "
              >Founder, ACME Inc.</span
            >
          </div>
        </div>
      </footer>
    </div>
  </div>
</a>
/**
 * card.css — Card ([data-ui~="card"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _properties.css (--_*-resolved)
 * @tokens     --ui-card-* (@layer variables; named after the utilities they back)
 * @presets    data-card-variant="outline" | "muted" | "floating"
 *
 * The card is a surface: a grid with a gap, the card colors, a radius, and
 * clipped overflow. The default card is bare (no padding, radius, or shadow), so
 * media can run edge to edge; each variant adds padding, the radius, and one
 * treatment. Every card keeps a 1px border, transparent unless a variant colors
 * it, so cards line up whatever their variant.
 * Its parts are plain elements laid out with utilities.
 * Every utility-backed declaration reads one chain, highest first:
 *   var(--_<utility>-resolved, var(--_card-<utility>, var(--ui-card-<utility>)))
 */

@layer variables {
  :root {
    /* a bare card has no surface; the outline and floating variants paint --color-card */
    --ui-card-bg: transparent;
    --ui-card-text: var(--color-card-foreground);
    --ui-card-rounded: var(--radius-lg);
    --ui-card-shadow: none;

    --ui-card-border-width: 1px;
    --ui-card-border-style: solid;
    --ui-card-border-color: transparent;

    /* dual-mode: a scale number or a length */
    --ui-card-gap: var(--space-sm);
    --ui-card-p: 0;
  }
}

/* Variant privates: non-inheriting so a variant never cascades into a nested card. */
@property --_card-bg {
  syntax: "*";
  inherits: false;
}

@property --_card-text {
  syntax: "*";
  inherits: false;
}

@property --_card-shadow {
  syntax: "*";
  inherits: false;
}

@property --_card-border-width {
  syntax: "*";
  inherits: false;
}

@property --_card-border-color {
  syntax: "*";
  inherits: false;
}

@property --_card-rounded {
  syntax: "*";
  inherits: false;
}

@property --_card-p {
  syntax: "*";
  inherits: false;
}

@layer ui.components {
  :where([data-ui~="card"]) {
    display: grid;
    --_gap-len: var(--_gap-resolved, var(--ui-card-gap));
    --_gap-num: var(--_gap-resolved, var(--ui-card-gap));
    gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
    --_p-len: var(--_p-resolved, var(--_card-p, var(--ui-card-p)));
    --_p-num: var(--_p-resolved, var(--_card-p, var(--ui-card-p)));
    padding: calc(var(--_p-len) + var(--_p-num) * var(--spacing));
    color: var(--_text-resolved, var(--_card-text, var(--ui-card-text)));
    background-color: var(--_bg-resolved, var(--_card-bg, var(--ui-card-bg)));
    border-width: var(
      --_border-width-resolved,
      var(--_card-border-width, var(--ui-card-border-width))
    );
    border-style: var(--_border-style-resolved, var(--ui-card-border-style));
    border-color: var(
      --_border-color-resolved,
      var(--_card-border-color, var(--ui-card-border-color))
    );
    border-radius: var(--_rounded-resolved, var(--_card-rounded, var(--ui-card-rounded)));
    box-shadow: var(--_shadow-resolved, var(--_card-shadow, var(--ui-card-shadow)));
    overflow: clip;
    transition: var(--default-transition);
  }

  /* ===========================================================================
  CARD VARIANTS — data-card-variant writes the privates
  =========================================================================== */

  :where([data-ui~="card"]:not([data-card-variant])) {
    --_card-rounded: 0;
  }

  :where([data-ui~="card"][data-card-variant="outline"]) {
    --_card-p: var(--space-md);
    --_card-border-color: var(--color-border);
  }

  :where([data-ui~="card"][data-card-variant="muted"]) {
    --_card-p: var(--space-md);
    --_card-bg: var(--color-muted);
    --_card-text: var(--color-foreground);
  }

  :where([data-ui~="card"][data-card-variant="floating"]) {
    --_card-p: var(--space-md);
    --_card-bg: var(--color-card);
    --_card-border-color: var(--color-border);
    --_card-text: var(--color-card-foreground);
    --_card-shadow: var(--shadow-sm);
  }
}

Subgrid

<div
  style="
    --display: grid;
    --grid-cols: 2;
    --grid-rows: 3;
    --gap: 6;
  "
>
  <a
    href="#"
    style="
      --display: grid;
      --row-span: 3;
      --template-rows: subgrid;
      --cursor: pointer;
    "
  >
    <div
      data-ui="card"
      style="
        --row-span: 3;
        --template-rows: subgrid;
      "
    >
      <figure
        style="
          --display: grid;
          --row-span: 1;
          --template-rows: subgrid;
          --aspect: 3 / 2;
          --rounded: var(--radius-md) var(--radius-md) 0 0;
          --overflow: clip;
        "
      >
        <img
          width="300"
          height="200"
          loading="lazy"
          src="https://images.unsplash.com/photo-1564760055775-d63b17a55c44?auto=format&fit=crop&w=300&h=200&q=60"
          alt="Elephants crossing a dusty riverbed"
          style="
            --size: 100%;
            --opacity: 1;
            --opacity--hover: 0.75;
            --scale: 1;
            --scale--hover: 1.02;
            --transition: var(--default-transition);
            --object-fit: cover;
          "
        />
      </figure>
      <div
        style="
          --display: grid;
          --row-span: 2;
          --template-rows: subgrid;
          --px: 4;
          --pb: 4;
          --gap: 4;
        "
      >
        <header
          style="
            --display: grid;
            --gap: 2;
          "
        >
          <div
            style="
              --display: flex;
              --items: center;
              --gap: 2;
            "
          >
            <span data-ui="badge" data-badge-variant="muted">Research</span>
          </div>
          <hgroup
            style="
              --display: grid;
              --gap: 2;
            "
          >
            <h3
              style="
                --font-size: var(--font-size-lg);
                --font-family: var(--font-family-heading);
                --leading: var(--leading-lg);
                --tracking: var(--tracking-lg);
              "
            >
              Migration patterns
            </h3>
            <p
              style="
                --font-size: var(--font-size-sm);
                --leading: var(--leading-sm);
                --tracking: var(--tracking-sm);
                --line-clamp: 2;
                --text: var(--color-muted-foreground);
              "
            >
              Satellite collars reveal ancient routes herds still follow each dry season across the
              floodplain.
            </p>
          </hgroup>
        </header>
        <footer
          style="
            --display: grid;
            --justify: start;
            --pt: 4;
            --gap: 4;
            --border-t: 1;
          "
        >
          <div
            style="
              --display: flex;
              --items: center;
              --gap: 2;
            "
          >
            <figure
              data-ui="pile"
              role="img"
              style="
                --display: grid;
                --size: 11;
                --border-color: var(--color-border);
                --border-width: 1px;
                --rounded: var(--radius-full);
                --overflow: clip;
              "
            >
              <img
                width="64"
                height="64"
                loading="lazy"
                src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2&w=64&h=64&q=90"
                alt="John Doe"
                aria-hidden="true"
                style="
                  --order: 9999;
                  --size: 100%;
                  --bg: transparent;
                  --object-fit: cover;
                "
              />
              <figcaption
                style="
                  --display: flex;
                  --items: center;
                  --justify: center;
                  --order: -9999;
                  --size: 100%;
                  --font-size: var(--font-size-md);
                  --font-weight: strong;
                  --leading: var(--leading-md);
                  --tracking: var(--tracking-md);
                  --text-align: center;
                  --text: var(--color-primary-foreground);
                  --bg: var(--color-primary);
                "
              >
                <span data-ui="sr-only">John Doe</span>
                <span aria-hidden="true">JD</span>
              </figcaption>
            </figure>
            <div style="--display: flex-col">
              <span
                style="
                  --font-size: var(--font-size-sm);
                  --font-weight: strong;
                  --leading: var(--leading-sm);
                  --leading: 1;
                  --tracking: var(--tracking-sm);
                "
                >John Doe</span
              >
              <span
                style="
                  --font-size: var(--font-size-xs);
                  --leading: var(--leading-xs);
                  --tracking: var(--tracking-xs);
                  --text: var(--color-muted-foreground);
                "
                >Founder, ACME Inc.</span
              >
            </div>
          </div>
        </footer>
      </div>
    </div>
  </a>

  <a
    href="#"
    style="
      --display: grid;
      --row-span: 3;
      --template-rows: subgrid;
      --cursor: pointer;
    "
  >
    <div
      data-ui="card"
      style="
        --row-span: 3;
        --template-rows: subgrid;
      "
    >
      <figure
        style="
          --display: grid;
          --row-span: 1;
          --template-rows: subgrid;
          --aspect: 3 / 2;
          --rounded: var(--radius-md) var(--radius-md) 0 0;
          --overflow: clip;
        "
      >
        <img
          width="300"
          height="200"
          loading="lazy"
          src="https://images.unsplash.com/photo-1500382017468-9049fed747ef?auto=format&fit=crop&w=300&h=200&q=60"
          alt="Zebras grazing at dawn on the savanna"
          style="
            --size: 100%;
            --opacity: 1;
            --opacity--hover: 0.75;
            --scale: 1;
            --scale--hover: 1.02;
            --transition: var(--default-transition);
            --object-fit: cover;
          "
        />
      </figure>
      <div
        style="
          --display: grid;
          --row-span: 2;
          --template-rows: subgrid;
          --px: 4;
          --pb: 4;
          --gap: 4;
        "
      >
        <header
          style="
            --display: grid;
            --gap: 2;
          "
        >
          <div
            style="
              --display: flex;
              --items: center;
              --gap: 2;
            "
          >
            <span data-ui="badge" data-badge-variant="muted">Field notes</span>
          </div>
          <hgroup
            style="
              --display: grid;
              --gap: 2;
            "
          >
            <h3
              style="
                --font-size: var(--font-size-lg);
                --font-family: var(--font-family-heading);
                --leading: var(--leading-lg);
                --tracking: var(--tracking-lg);
              "
            >
              How quickly zebras jump
            </h3>
            <p
              style="
                --font-size: var(--font-size-sm);
                --leading: var(--leading-sm);
                --tracking: var(--tracking-sm);
                --line-clamp: 2;
                --text: var(--color-muted-foreground);
              "
            >
              Striped coats and sudden bursts of speed help herds evade predators when the
              grasslands go quiet at dusk.
            </p>
          </hgroup>
        </header>
        <footer
          style="
            --display: grid;
            --justify: end;
            --pt: 4;
            --pb: 2;
            --gap: 4;
            --border-t: 1;
          "
        >
          <div data-ui="button" data-button-variant="link">
            Read article
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
              <rect width="256" height="256" fill="none" />
              <line
                x1="40"
                y1="128"
                x2="216"
                y2="128"
                fill="none"
                stroke="currentColor"
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="16"
              />
              <polyline
                points="144 56 216 128 144 200"
                fill="none"
                stroke="currentColor"
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="16"
              />
            </svg>
          </div>
        </footer>
      </div>
    </div>
  </a>
</div>
/**
 * card.css — Card ([data-ui~="card"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _properties.css (--_*-resolved)
 * @tokens     --ui-card-* (@layer variables; named after the utilities they back)
 * @presets    data-card-variant="outline" | "muted" | "floating"
 *
 * The card is a surface: a grid with a gap, the card colors, a radius, and
 * clipped overflow. The default card is bare (no padding, radius, or shadow), so
 * media can run edge to edge; each variant adds padding, the radius, and one
 * treatment. Every card keeps a 1px border, transparent unless a variant colors
 * it, so cards line up whatever their variant.
 * Its parts are plain elements laid out with utilities.
 * Every utility-backed declaration reads one chain, highest first:
 *   var(--_<utility>-resolved, var(--_card-<utility>, var(--ui-card-<utility>)))
 */

@layer variables {
  :root {
    /* a bare card has no surface; the outline and floating variants paint --color-card */
    --ui-card-bg: transparent;
    --ui-card-text: var(--color-card-foreground);
    --ui-card-rounded: var(--radius-lg);
    --ui-card-shadow: none;

    --ui-card-border-width: 1px;
    --ui-card-border-style: solid;
    --ui-card-border-color: transparent;

    /* dual-mode: a scale number or a length */
    --ui-card-gap: var(--space-sm);
    --ui-card-p: 0;
  }
}

/* Variant privates: non-inheriting so a variant never cascades into a nested card. */
@property --_card-bg {
  syntax: "*";
  inherits: false;
}

@property --_card-text {
  syntax: "*";
  inherits: false;
}

@property --_card-shadow {
  syntax: "*";
  inherits: false;
}

@property --_card-border-width {
  syntax: "*";
  inherits: false;
}

@property --_card-border-color {
  syntax: "*";
  inherits: false;
}

@property --_card-rounded {
  syntax: "*";
  inherits: false;
}

@property --_card-p {
  syntax: "*";
  inherits: false;
}

@layer ui.components {
  :where([data-ui~="card"]) {
    display: grid;
    --_gap-len: var(--_gap-resolved, var(--ui-card-gap));
    --_gap-num: var(--_gap-resolved, var(--ui-card-gap));
    gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
    --_p-len: var(--_p-resolved, var(--_card-p, var(--ui-card-p)));
    --_p-num: var(--_p-resolved, var(--_card-p, var(--ui-card-p)));
    padding: calc(var(--_p-len) + var(--_p-num) * var(--spacing));
    color: var(--_text-resolved, var(--_card-text, var(--ui-card-text)));
    background-color: var(--_bg-resolved, var(--_card-bg, var(--ui-card-bg)));
    border-width: var(
      --_border-width-resolved,
      var(--_card-border-width, var(--ui-card-border-width))
    );
    border-style: var(--_border-style-resolved, var(--ui-card-border-style));
    border-color: var(
      --_border-color-resolved,
      var(--_card-border-color, var(--ui-card-border-color))
    );
    border-radius: var(--_rounded-resolved, var(--_card-rounded, var(--ui-card-rounded)));
    box-shadow: var(--_shadow-resolved, var(--_card-shadow, var(--ui-card-shadow)));
    overflow: clip;
    transition: var(--default-transition);
  }

  /* ===========================================================================
  CARD VARIANTS — data-card-variant writes the privates
  =========================================================================== */

  :where([data-ui~="card"]:not([data-card-variant])) {
    --_card-rounded: 0;
  }

  :where([data-ui~="card"][data-card-variant="outline"]) {
    --_card-p: var(--space-md);
    --_card-border-color: var(--color-border);
  }

  :where([data-ui~="card"][data-card-variant="muted"]) {
    --_card-p: var(--space-md);
    --_card-bg: var(--color-muted);
    --_card-text: var(--color-foreground);
  }

  :where([data-ui~="card"][data-card-variant="floating"]) {
    --_card-p: var(--space-md);
    --_card-bg: var(--color-card);
    --_card-border-color: var(--color-border);
    --_card-text: var(--color-card-foreground);
    --_card-shadow: var(--shadow-sm);
  }
}

Attributes

card attributes
AttributeKindValuesNotes
data-ui="card"Identityfree valueIdentity token in data-ui.
data-card-variantPresetfloating muted outlinePreset: a named bundle of values.

Hooks

Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.

card hooks
HookDefaultState
--ui-card-bgtransparent—
--ui-card-border-colortransparent—
--ui-card-border-stylesolid—
--ui-card-border-width1px—
--ui-card-gapvar(--space-sm)—
--ui-card-p0—
--ui-card-roundedvar(--radius-lg)—
--ui-card-shadownone—
--ui-card-textvar(--color-card-foreground)—