Zetta v1.6

Components · Data display & feedback

Card

Bordered structural containers. Four variants share an 8px radius and 1px border — only padding and border color change. No shadows at rest: elevation is reserved for overlays.

Preview

Standard card

24px padding, hairline border, no shadow — the default container.

Compact

Tighter 8px padding for dense layouts.

Secondary

Muted surface for nested or secondary panels.

Announcement

High-contrast border for major notices — one per view.

Interactive

Clickable card — hover and focus states.

Selected

Primary tint + primary border marks the active card.

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "Card" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `card` spec (variants: card, card-compact, card-secondary, card-announcement) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `card` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

┌─ card ──────────────────────────────────────┐  ← 1px {colors.hairline} border
│                                             │     {rounded.base} 8px radius
│   [header region — product-defined]         │
│   ─────────────────────────────────         │
│   [content region — product-defined]        │
│                                             │
│   [footer region — product-defined]         │
└─────────────────────────────────────────────┘

Parts:

  • Surface — carries fill, border, radius, and optional shadow
  • Padding zone — uniform on all sides, set by variant
  • Header / Content / Footer — layout regions; not tokenized at card level, managed by child components
State Background Shadow Notes
Default {colors.surface-card} {elevation.shadow.raised} Standard resting card
Hover (interactive card) {colors.surface-muted} {elevation.shadow.raised} Only when card itself is clickable
Focus (interactive card) {colors.surface-card} + 2px solid {colors.border-focus} {elevation.shadow.raised} Keyboard only
Selected (interactive card) {colors.primary-bg} {elevation.shadow.raised} border: 1px solid {colors.primary}
Disabled {colors.disabled-bg} None 50% opacity on content
Loading Skeleton fills content area {elevation.shadow.raised}

When to use

  • Tier 1 (standard): the primary grouping container for dashboard tiles, form sections, and content panels.
  • Tier 2 (compact): dense layouts where the card chrome is present but padding is owned by child components.
  • Tier 3 (secondary): nested information inside a standard card, or lower-emphasis sections alongside primary cards.
  • Tier 4 (announcement): one-time system-level messages, release notes, or onboarding milestones. Never for error states — use a feedback alert.

When not to use

  • Do not use a card as a navigation element — it is a content container, not a link.
  • Do not nest more than 2 card levels (standard containing secondary). Deeper nesting creates visual ambiguity.
  • Do not use card-announcement for routine information — it loses its signal value if overused.
  • Do not apply shadows to multiple cards in the same grid — it removes the elevation differentiation.

Do

  • Use consistent card variants within the same section or grid.
  • Apply {elevation.shadow.raised} only when the card must visually lift above a sunken context.
  • Use card-compact when child components (table, list) already provide their own internal spacing.
  • Limit card-announcement to one instance per view.

Don't

  • Don't mix tiers arbitrarily — define which tier maps to which content pattern and apply it consistently.
  • Don't put a shadow on a card that sits on {colors.surface-card} (same elevation) — shadow requires a lower surrounding surface.
  • Don't add card-announcement styling to inline validation or feedback messages — use the feedback/alert component.
  • Card headers should be short noun phrases (not sentences).
  • Body content uses {colors.body} / 14px / 400 Geist — do not override font inside cards.
  • Use a separator ({colors.hairline}, 1px) to divide header, content, and footer regions when needed. Do not use borders on child components for this purpose.

Keyboard

Card is a layout component with no keyboard behavior of its own. When used as an interactive card (clickable), wrap it in a <button> or <a> — keyboard interaction is then standard button or link behavior: Tab moves focus to the card, and Enter / Space activates it when the interactive prop is set.

Accessibility

  • Non-interactive cards: no role required. The card is a layout container.
  • Interactive cards: add role="button" and tabindex="0". Set aria-label describing what happens on activation.
  • Announcement cards: add role="region" with aria-label describing the announcement type (e.g., aria-label="System announcement").
  • Focus ring: interactive cards show 2px {colors.border-focus} outline at 2px offset — identical to button focus.
  • Contrast: {colors.hairline} border achieves 3:1 against {colors.surface-card} in light mode (WCAG AA — UI components boundary). {colors.border-announcement} achieves 3:1 minimum.

Spec

Values and token references resolved straight from the Zetta spec.

card
backgroundColor
{colors.surface-card}
textColor
{colors.ink}
borderColor
{colors.hairline}
borderWidth
1px
borderRadius
{rounded.base}
padding
{spacing.page}
shadow
none
card-compact
backgroundColor
{colors.surface-card}
textColor
{colors.ink}
borderColor
{colors.hairline}
borderWidth
1px
borderRadius
{rounded.base}
padding
{spacing.sm}
shadow
none
card-secondary
backgroundColor
{colors.surface-secondary}
textColor
{colors.ink}
borderColor
{colors.hairline}
borderWidth
1px
borderRadius
{rounded.base}
padding
{spacing.md}
shadow
none
card-announcement
backgroundColor
{colors.surface-card}
textColor
{colors.ink}
borderColor
{colors.border-announcement}
borderWidth
1px
borderRadius
{rounded.base}
padding
{spacing.page}
shadow
none

Known gaps

  • Interactive / clickable card variant (card-interactive) — hover and focus state tokens not yet formalized. Current guidance: {colors.surface-secondary} on hover, {colors.border-focus} outline on focus.
  • Card header and footer sub-regions — spacing and divider treatment not tokenized at card level.
  • Card loading skeleton — skeleton token exists in zetta-feedback.md; card-level loading pattern not yet specified.