Zetta v1.6

Components · Data display & feedback

Badge

Small, non-interactive status labels. Six variants share one geometry — only fill and text color change. Always pair a semantic badge with a text label.

Preview

Neutral Primary Success Warning Danger Info

Status icon legend

Success — action completed or status is healthy

Warning — attention required, not yet critical

Error — action failed or status is critical

Information — neutral notice or guidance

Neutral — no status assigned

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 "Badge" 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 `badge` spec (variants: neutral, primary, success, warning, danger, info) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `badge` 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

╭──────────────╮
│  ● Label     │  ← {rounded.full}, 12px Geist, 10px × 4px padding
╰──────────────╯
  ↑ optional leading icon (12px, inherits textColor)

Parts:

  • Container — pill surface with fill, border, and radius
  • Label — 12px / 400 Geist, centered
  • Leading icon (optional) — 12px, inherits textColor, 4px gap

Badge is non-interactive. It has no hover, focus, or active states in standard use.

State Visual
Default Semantic fill + text + border (see variant matrix in frontmatter)
Disabled 50% opacity on fill and text — use sparingly

For dismissible badge interaction states, see Dismissible Badge section below.

When to use

  • Status labels on list rows, table cells, or card headers: "Active", "Pending", "Overdue".
  • Numeric counts embedded in a surface — use badge-danger for unread notifications or error counts.
  • Classification tags on content items (categories, priority levels).

When not to use

  • Clickable tags or filters — use Tag instead.
  • Long-form status labels (over 3 words) — use Lozenge for wider labels.
  • Standalone numeric counters on nav items — use a notification dot or Badge positioned as an overlay on the nav icon.

Do

  • Always include a text label — never use a badge with color alone.
  • Match semantic variant to the status: success = completed/approved, danger = failed/critical, warning = at-risk/pending.
  • Keep label text to 1–3 words maximum.

Don't

  • Don't add hover, focus, or active states — badges are static.
  • Don't use more than 2 badge variants in the same row — visual noise overwhelms the data.
  • Don't use badge-primary (Lime fill) for status — it's for featured/active classification, not semantic meaning.
  • Label format: noun or short noun phrase. Title case. No verbs. "In Review", "Approved", "Overdue".
  • Numeric badges: locale-aware formatting. Abbreviate at 1000+: "1.2k", "24k". Never show decimals on counts.
  • Avoid color-only labeling: never use "Green badge" or "Red badge" in documentation — use the semantic variant name.

Keyboard

Standard badge is non-interactive. For the dismissible badge variant:

Key Behavior
Tab Move focus to the remove button
Enter / Space Remove the badge

Accessibility

  • ARIA: badges are typically presentational. Add aria-label on the badge container only when the text alone is insufficient to convey meaning (e.g. a count badge overlaid on a bell icon: aria-label="3 unread notifications").
  • Color independence: in the accessibility theme, badge-neutral-accessibility removes the fill and uses a white border — the text label is the only differentiator. Implementations must not rely on fill color to distinguish badge variants programmatically.
  • Icon: aria-hidden="true" on leading icons — the text label carries the semantic meaning.
  • Contrast: all text/background pairs across all six variants and all three themes must meet 4.5:1 (WCAG AA). Verified at design-token level.

Spec

Values and token references resolved straight from the Zetta spec.

badge-neutral
backgroundColor
{colors.surface-muted}
textColor
{colors.badge-neutral-text}
borderColor
{colors.border-strong}
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-neutral-accessibility
backgroundColor
transparent
textColor
{colors.ink}
borderColor
{colors.border-strong}
borderWidth
1px
badge-primary
backgroundColor
{colors.brand}
textColor
{colors.brand-text}
borderColor
rgba(0,0,0,0.15)
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-success
backgroundColor
{colors.success-bg}
textColor
{colors.success-text}
borderColor
{colors.success}
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-warning
backgroundColor
{colors.warning-bg}
textColor
{colors.warning-text}
borderColor
{colors.warning}
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-danger
backgroundColor
{colors.danger-bg}
textColor
{colors.danger-text}
borderColor
{colors.danger}
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-info
backgroundColor
{colors.info-bg}
textColor
{colors.info-text}
borderColor
{colors.info}
borderWidth
1px
borderRadius
{rounded.full}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
paddingX
{spacing.sm}
paddingY
{spacing.xs}
badge-remove
iconSize
10px
marginLeft
{spacing.xs}
cursor
pointer
borderRadius
{rounded.full}
padding
2px
display
inline-flex
alignItems
center
justifyContent
center
badge-remove-hover
backgroundColor
rgba(0,0,0,0.12)

Known gaps

  • Interactive badge hover shift removed in accessibility theme — hover uses {colors.hover-bg} (#ffffff) + {colors.hover-text} (#000000) uniformly.