Default
Show the codeHide the code
Default, code
<Badge>Active</Badge>A short status or count, never interactive. Text is 7:1 on its tint; counts carry a hidden label.
These are the real React components. Use them with a mouse, touch or keyboard, and switch the theme and density for every example at once.
Default, code
<Badge>Active</Badge>Tones, code
<div className="flex flex-wrap gap-gap-sm">
<Badge>Draft</Badge><Badge tone="brand">New</Badge><Badge tone="success">Paid</Badge>
<Badge tone="warning">Due soon</Badge><Badge tone="danger">Overdue</Badge><Badge tone="info">Beta</Badge><Badge tone="strong">Save 50%</Badge>
</div>Strong on brand, code
<div className="flex items-center gap-gap-sm rounded-surface border-2 border-line-brand bg-surface-brand-subtle p-[var(--sd-spacing-inset-md)] text-body-md text-fg">
<span className="font-semibold">Annual</span><Badge tone="strong">Save 50%</Badge>
</div>Count, code
<Badge tone="brand" countLabel="unread messages">3</Badge>Kinds, code
<div className="flex flex-col gap-gap-md">
{(['neutral', 'brand', 'success', 'warning', 'danger', 'info', 'strong'] as const).map((tone) => (
<div key={tone} className="flex items-center gap-gap-sm">
<Badge tone={tone}>Status</Badge>
<Badge tone={tone} kind="count" countLabel="unread messages">{3}</Badge>
<Badge tone={tone} kind="count" countLabel="unread messages">{120}</Badge>
<Badge tone={tone} kind="dot">{`${tone} status`}</Badge>
</div>
))}
</div>Anchored, code
<div className="flex items-center gap-gap-lg p-[var(--sd-spacing-inset-md)]">
<span className="relative inline-flex">
<IconButton label="Notifications" icon={Bell} variant="ghost" />
<Badge tone="danger" kind="count" anchored countLabel="unread notifications">{3}</Badge>
</span>
<span className="relative inline-flex">
<IconButton label="Messages" icon={Bell} variant="ghost" />
<Badge tone="danger" kind="count" anchored countLabel="unread messages">{240}</Badge>
</span>
<span className="relative inline-flex">
<Avatar name="Priya Shah" />
<Badge tone="success" kind="dot" anchored>New activity</Badge>
</span>
</div>The 17 design tokens Badge reads, with the value each has in the theme and density chosen above. Change a token and the component follows, in Figma and in code.
| Token | Value now | CSS variable |
|---|---|---|
| badge.brand.bg | … | --sd-badge-brand-bg |
| badge.brand.fg | … | --sd-badge-brand-fg |
| badge.danger.bg | … | --sd-badge-danger-bg |
| badge.danger.fg | … | --sd-badge-danger-fg |
| badge.info.bg | … | --sd-badge-info-bg |
| badge.info.fg | … | --sd-badge-info-fg |
| badge.neutral.bg | … | --sd-badge-neutral-bg |
| badge.neutral.fg | … | --sd-badge-neutral-fg |
| badge.radius | … | --sd-badge-radius |
| badge.strong.bg | … | --sd-badge-strong-bg |
| badge.strong.fg | … | --sd-badge-strong-fg |
| badge.success.bg | … | --sd-badge-success-bg |
| badge.success.fg | … | --sd-badge-success-fg |
| badge.warning.bg | … | --sd-badge-warning-bg |
| badge.warning.fg | … | --sd-badge-warning-fg |
| color.bg.canvas | … | --sd-color-bg-canvas |
| color.border.subtle | … | --sd-color-border-subtle |
The npm package isn’t published yet. When it is, this is the install line; until then, this page runs the same build of the package (from commit fa3abf8).
Install
npm i @superduperui/react @superduperui/tokensImport
import { Badge } from "@superduperui/react"Badge also takes the props of the React Aria component it’s built on.
| Prop | Type | What it does |
|---|---|---|
tone | 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'strong'Default 'neutral' | Colour family. The text always says what the colour means (1.4.1). Default neutral. strong is emphasis, not a status: a solid primary fill for one callout on a selected or brand surface. |
kind | 'label' | 'count' | 'dot'Default 'label' | label (default): text on a tint. count: a number, at least a 24px circle, capped at `max` ("99+"); give it a countLabel. dot: an 8px mark in the tone colour; its children are read by screen readers only. |
childrenrequired | ReactNode | The visible text; the number for `kind="count"`; the hidden name for `kind="dot"`. |
countLabel | string | For count badges ("3"): what is being counted, read by screen readers after the number ("3 unread messages"). Required whenever the visible text is a bare number. |
max | numberDefault 99 | Count only: a larger number shows as "max+" ("99+"). Default 99. |
anchored | booleanDefault false | Positions the badge over the top-end corner of its `relative` parent (an icon button, an avatar) with a 2px canvas ring; it grows towards the end. |
Screen readers announce "Paid"; a count reads "3 unread messages" ("99+ unread messages" over max); a dot reads its name ("Online").
WCAG 2.2 AAA criteria the component meets in Comfortable density: 1.4.6, 1.4.9, 2.3.2.
Not interactive: density changes spacing only.
| axe-core (WCAG A, AA, AAA and best practice) | 140 runs across every story, theme, density and width: 0 violations |
|---|---|
| Rendered text contrast | 450 text runs: 0 below 7:1, lowest 7.79:1 |
| Target size | 4 targets: 0 under 44px in Comfortable |
| Focus appearance (2.4.13) | 0 focus stops in 0 browser engines: all pass |
Manual passes with VoiceOver and NVDA haven’t been done yet.
The Figma component is Badge, with Tone (Neutral, Brand, Success, Warning, Danger, Info, Strong); Kind (Label, Count, Dot). The Figma library isn’t published yet.
Figma to code: Tone → tone (Neutral = "neutral", Brand = "brand", Success = "success", Warning = "warning", Danger = "danger", Info = "info", Strong = "strong"); Label → children; Kind → kind (Label = "label", Count = "count", Dot = "dot"); Count → children when Kind=Count; Count label → countLabel when Kind=Count; Anchored → anchored.