API
Card
A surface that groups related content.
Installation
pnpm dlx zazz-ui add cardnpx zazz-ui add cardyarn dlx zazz-ui add cardbunx zazz-ui add cardRun 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
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="card" | Identity | free value | Identity token in data-ui. |
data-card-variant | Preset | floating muted outline | Preset: 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.
| Hook | Default | State |
|---|---|---|
--ui-card-bg | transparent | — |
--ui-card-border-color | transparent | — |
--ui-card-border-style | solid | — |
--ui-card-border-width | 1px | — |
--ui-card-gap | var(--space-sm) | — |
--ui-card-p | 0 | — |
--ui-card-rounded | var(--radius-lg) | — |
--ui-card-shadow | none | — |
--ui-card-text | var(--color-card-foreground) | — |