Badge
Badge is a short, non-interactive label for a status, category or count. Use it to highlight state without taking action.
Beta until text contrast is resolved (most Filled and Lighter colours, the default gray Filled badge and three Light status badges are below 4.5:1, pending a decision), design confirms four colour bindings, and the sizes are measured in a browser. Everything else on this page is implemented and tested.
Preview
Badge and Status badge share one palette. Change the controls to try every supported combination; the preview renders the real component and the code updates with it.
Badge
import { Badge } from '@nuton/ui'
<Badge appearance="light" color="blue">New</Badge>Status badge
import { StatusBadge } from '@nuton/ui'
<StatusBadge status="completed">Completed</StatusBadge>Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer dependencies: react and react-dom 18 or later. Status badge draws its default icons with @remixicon/react, and you pass Remix icons to leadingIcon, trailingIcon and icon (D1).
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)Badges use the Label and Subheading type tokens, which read --nu-font-body (Inter). Map it to your loaded font as described on the Button page.
Usage
import { Badge, StatusBadge } from '@nuton/ui'
export function LessonRow({ title, isNew, done }: { title: string; isNew: boolean; done: boolean }) {
return (
<li>
{title}
{isNew && <Badge appearance="light" color="blue">New</Badge>}
{done
? <StatusBadge status="completed">Completed</StatusBadge>
: <StatusBadge status="information">In progress</StatusBadge>}
</li>
)
}- The default is gray, filled, md. Choose
appearanceandcolorfor meaning;appearance="light"passes AA in every colour today. - Use
StatusBadgefor the status of an item (a submission, a lesson). Its icon shape is a second cue besides colour. - Write the meaning in words. Colour is never the only cue, so “Failed”, not a red dot.
- Give a numeric badge a
label, such as “3 to review”. A bare number means nothing to a screen reader. - Badges aren't interactive. For something people click or remove, use Tag or Button.
Variants and sizes
Appearance sets the emphasis and colour sets the meaning. Every colour works with every appearance and size. Status badge is a separate component with a fixed colour per status.
Colour × appearance
Contrast: every appearance meets AA (C47, 2026-10-14)
Figma's Base colours pass 4.5:1 only for blue and purple (gray 4.23:1, red 4.15:1, teal 4.13:1, pink 3.91:1, orange 3.44:1, green 2.85:1, sky 2.53:1, yellow 2.10:1). Code uses a stronger step of each hue, --nu-badge-strong, for Filled fills, Lighter and Stroke text and Status badge text: gray 600, orange 800, red 700, green 800, yellow 900, sky 900, pink 800 and teal 800, with blue and purple unchanged. White on Filled is 4.93:1 or more; text on Lighter 4.53:1 or more. Light already passed.
Figma bindings not copied
Figma fills red Lighter with State/Error/Light and binds teal Light and Lighter to Success tokens, so they look like mistakes. Code follows the same rule for every colour instead (FC-32). Sky reads Foundation sky 50, 100 and 900 directly because no State role exists for it (C36).
Sizes
sm · 16pxmd · 24pxlg · 28px<Badge appearance="light" color="blue" size="sm">New</Badge>
<Badge appearance="light" color="blue" size="md">New</Badge>
<Badge appearance="light" color="blue" size="lg">New</Badge>| Size | Height | Padding | Icon (inset) | Dot inset | Numeric | Type |
|---|---|---|---|---|---|---|
sm | 16 | 2 / 8 | 12 (4) | 0 | 16 × 16 | Subheading/2X Small (11/12), uppercase |
md | 24 | 2 / 8 | 16 (4) | 2 | 20 × 24 | Label/X Small (12/20) |
lg | 28 | 4 / 10 | 16 (10) | 10 | 28 × 28 | Label/Small (14/20) |
Heights follow Figma's geometry and are computed from the CSS; they haven't been measured in a browser yet. Figma's labels are wrong: “Medium (20)” measures 24 and “Large (32)” measures 28. Code uses the measured geometry, the C4 and C17 principle; correcting the labels is pending a decision. All sizes are pills with a 2px gap; the dot is a 4px circle in a 16px frame.
Types
<Badge appearance="light" color="purple">Basic</Badge>
<Badge appearance="light" color="green" dot>Live</Badge>
<Badge appearance="light" color="blue" leadingIcon={<RiSparkling2Line />}>AI summary</Badge>
<Badge appearance="light" color="gray" trailingIcon={<RiArrowRightSLine />}>3 spaces</Badge>
<Badge appearance="light" color="red" numeric label="5 unread messages">5</Badge>numeric ignores dot and both icons. The dot and icons take the text colour and are hidden from screen readers.
Status badge
completedCompletedCompletedCompletedCompletedpendingPending reviewPending reviewPending reviewPending reviewfailedFailedFailedFailedFailedinformationIn progressIn progressIn progressIn progressdisabledArchivedArchivedArchivedArchived| Status | Colour | Default icon | Nuton example |
|---|---|---|---|
completed | Success (green) | Checkbox circle | A finished lesson or a graded quiz |
pending | Warning (orange) | Alert | A submission waiting for review |
failed | Error (red) | Error warning | An upload or a generation that didn't finish |
information | Primary (blue) | Information | A lesson in progress |
disabled | Faded (gray) | Forbid | An archived space |
28px tall, radius 6, padding 4 / 8 / 4 / 4, Label/X Small. The icon is 16px with a 4px gap; with dot, a 6px dot in a 16px frame replaces it with a 2px gap. Stroke has a white fill, a Soft-200 border and Sub-600 text; only the icon or dot keeps the status colour. Pass icon to replace the default icon, for example <RiTimeLine /> for a scheduled lesson.
<StatusBadge status="information" icon={<RiTimeLine />}>Scheduled</StatusBadge>States and interactions
Badges don't respond to the pointer or keyboard. Their only state is disabled; a status badge's status is content, not a state.
The disabled Large badge in Figma uses the remote “Label/Small” text style, which renders 2px narrower. Code uses the local style (pending a decision).
Palette and component tokens
Both components set data-color and read one shared palette. Override the component tokens on a wrapper only for a documented exception.
| Token | Value | Used by |
|---|---|---|
--nu-badge-base · -light · -lighter · -dark · -strong | State/<role>/Base, Light, Lighter and Dark for the colour (gray Faded, blue Primary, orange Warning, red Error, green Success, yellow Pending, purple Purple, pink Feature, teal Teal; sky reads Foundation sky 600, 100, 50 and 900). Strong is the first step of the hue at 4.5:1 (C47) | Badge and Status badge |
--nu-badge-bg | Filled Strong · Light Light · Lighter Lighter · Stroke none · Disabled none | Badge |
--nu-badge-fg | Filled Bg/White · Light Dark · Lighter Strong · Stroke Strong · Disabled Text/Disabled-500 | Badge |
--nu-badge-border | Stroke Base · Disabled Stroke/Soft-200 · otherwise none | Badge |
--nu-status-badge-bg | Light Lighter · Stroke Bg/White | Status badge |
--nu-status-badge-fg | Base (icon and dot; Light text). Stroke text, and Light disabled text, is Text/Sub-600 | Status badge |
--nu-status-badge-border | Stroke Stroke/Soft-200 · Light none | Status badge |
Composition examples
Lesson progress
A status badge on each row tells students where they are. The text names the status; the icon shape repeats it without colour.
Space header
Category and plan badges sit next to a title. Keep it to one or two; more becomes noise.
Counts
A numeric badge shows how many items need attention. Its label gives screen readers the full meaning, and a status region announces changes.
Semantics
Badge and Status badge render a <span> with no role. They're read as part of the surrounding text, so place them where that reading makes sense (after the title they describe). Don't put a badge inside a button as its only label, and don't add onClick to it: a clickable badge isn't keyboard-accessible. For a count on a button, give the button an aria-label that includes the number.
API reference
Badge props
| Prop | Type | Default | Description |
|---|---|---|---|
appearance | 'filled' | 'light' | 'lighter' | 'stroke' | 'filled' | Figma Style: emphasis. |
color | 'gray' | 'blue' | 'orange' | 'red' | 'green' | 'yellow' | 'purple' | 'sky' | 'pink' | 'teal' | 'gray' | Figma Color (BadgeColor). Replaces the native color attribute. |
size | 'sm' | 'md' | 'lg' | 'md' | Height 16 / 24 / 28px. sm is 11px uppercase. |
dot | boolean | false | Figma Type=With Dot: a small dot before the text, in the text colour. Decorative. Ignored when numeric. |
leadingIcon | ReactNode | — | Figma Type=Left Icon. Rendered aria-hidden. Ignored when numeric. |
trailingIcon | ReactNode | — | Figma Type=Right Icon. Rendered aria-hidden. Ignored when numeric. |
numeric | boolean | false | Figma Number=On: compact, centred, tabular figures. Children are the number; pair it with label. |
disabled | boolean | false | Figma Disabled=On: outline and Disabled-500 text for every appearance and colour. Visual only. |
label | string | — | Text for assistive technology that replaces the visible content, such as “3 to review”. The visible content is then hidden from screen readers. |
children | ReactNode | — | The text, or the number when numeric. |
ref | Ref<HTMLSpanElement> | — | Forwarded to the span. |
…rest | Omit<HTMLAttributes<HTMLSpanElement>, 'color'> | — | Any other span attribute (id, title, data-*, aria-*…). |
StatusBadge props
| Prop | Type | Default | Description |
|---|---|---|---|
statusrequired | 'completed' | 'pending' | 'failed' | 'information' | 'disabled' | — | Figma Status (StatusBadgeStatus). Sets the colour and the default icon. |
childrenrequired | ReactNode | — | The status in words, such as "Completed". The icon and colour aren’t announced. |
appearance | 'light' | 'stroke' | 'light' | Figma Style: tinted with coloured text, or white with a Soft-200 border and Sub-600 text. |
dot | boolean | false | Figma With Dot=On: a 6px dot replaces the icon. |
icon | ReactNode | the status icon | Replaces the default icon (a Remix icon). Rendered aria-hidden. |
ref | Ref<HTMLSpanElement> | — | Forwarded to the span. |
…rest | HTMLAttributes<HTMLSpanElement> | — | Any other span attribute (id, title, aria-*…). |
Styling hooks
Badge renders .nu-badge with data-appearance, data-color, data-size, data-numeric, data-dot, data-leading, data-trailing and data-disabled. Status badge renders .nu-status-badge with data-status, data-color, data-appearance and data-dot. Use props rather than styling these. The --nu-badge-* and --nu-status-badge-* tokens are the supported override points.
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Skips badges: they aren’t interactive or focusable. Focus goes to the controls around them. |
Screen readers
Read as inline text with no role. Dots and icons are hidden. With label, only the label is read. Changes to a count aren't announced on their own; use a status region.
Focus
No focus state, by design. Never make a badge clickable; put the action in a Button or Tag next to it.
Contrast and colour
Every colour and appearance meets 4.5:1 (C47): Filled 4.93:1 or more, Lighter 4.53:1 or more, Light 5.46:1 or more. Status badge text uses the strong step too (completed 5.19:1, pending 5.12:1, failed 4.59:1); Stroke text is 6.69:1. Disabled text is 1.53:1, exempt as inactive (C8). In forced colours, badges keep an outline.
Touch and pointer
Not a target, so target size doesn't apply. Don't place badges where a tap could be mistaken for an action.
You must: write the meaning in words, never colour alone; give numeric badges a label; announce changing counts in a status region; keep actions out of badges.
Figma mapping
| Figma property | Figma values | Code |
|---|---|---|
| Badge · Type | Basic · With Dot · Left Icon · Right Icon | dot, leadingIcon, trailingIcon |
| Badge · 🏵️ Style | Filled · Light · Lighter · Stroke | appearance |
| Badge · 🎨 Color | Gray · Blue · Orange · Red · Green · Yellow · Purple · Sky · Pink · Teal | color |
| Badge · 📏 Size | Small (16) · Medium (20) · Large (32), labels wrong | size: sm · md · lg = 16 · 24 · 28 |
| Badge · Number | Off · On | numeric (+ label, code-only) |
| Badge · Disabled | Off · On | disabled |
| Status Badge · Status | Completed · Pending · Failed · Information · Disabled | status |
| Status Badge · Style | Light · Stroke | appearance |
| Status Badge · With Dot | Off · On | dot; icon overrides the icon (code-only) |
Guidelines and related components
Use Badge for
- A short category or plan next to a title: “New”, “Pro”.
- A count that needs attention, with a
label. - The status of an item, with Status badge.
Do and don't
Common mistakes
- Showing a number with no
label, so screen readers announce just “3”. - Adding
onClickto a badge to make it a filter. Use a toggle Tag. - Using
disabledto mean “archived”. It's visual only and low contrast; use Status badgestatus="disabled"with text. - Picking a colour for decoration. Colours should map to meaning consistently across a product.