Badge

Badge is a short, non-interactive label for a status, category or count. Use it to highlight state without taking action.

  • Beta
  • Accessibility
  • Figma Badges 125:9055
  • packages/ui/src/components/Badge

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

New
tsx
import { Badge } from '@nuton/ui'

<Badge appearance="light" color="blue">New</Badge>

Status badge

Completed
tsx
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

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react

Peer 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

tsx
// 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

tsx
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 appearance and color for meaning; appearance="light" passes AA in every colour today.
  • Use StatusBadge for 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

ColourFilledLightLighterStroke
GrayGrayGrayGrayGray
BlueBlueBlueBlueBlue
OrangeOrangeOrangeOrangeOrange
RedRedRedRedRed
GreenGreenGreenGreenGreen
YellowYellowYellowYellowYellow
PurplePurplePurplePurplePurple
SkySkySkySkySky
PinkPinkPinkPinkPink
TealTealTealTealTeal

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

New
sm · 16px
New
md · 24px
New
lg · 28px
SizeHeightPaddingIcon (inset)Dot insetNumericType
sm162 / 812 (4)016 × 16Subheading/2X Small (11/12), uppercase
md242 / 816 (4)220 × 24Label/X Small (12/20)
lg284 / 1016 (10)1028 × 28Label/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

BasicLiveAI summary3 spaces5 unread messages

numeric ignores dot and both icons. The dot and icons take the text colour and are hidden from screen readers.

Status badge

StatusLightStrokeLight, dotStroke, dot
completedCompletedCompletedCompletedCompleted
pendingPending reviewPending reviewPending reviewPending review
failedFailedFailedFailedFailed
informationIn progressIn progressIn progressIn progress
disabledArchivedArchivedArchivedArchived
StatusColourDefault iconNuton example
completedSuccess (green)Checkbox circleA finished lesson or a graded quiz
pendingWarning (orange)AlertA submission waiting for review
failedError (red)Error warningAn upload or a generation that didn't finish
informationPrimary (blue)InformationA lesson in progress
disabledFaded (gray)ForbidAn 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.

Scheduled

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.

NewProBeta
DefaultFill, text and border come from the appearance and colour.
NewProBeta
DisabledThe same for every appearance and colour: no fill, 1px Stroke/Soft-200, Text/Disabled-500 (4.23:1, C8).
Not interactive
Hover and focusNone, by design. A badge isn't focusable and has no hover style. Put actions in a Tag or Button.

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.

TokenValueUsed by
--nu-badge-base · -light · -lighter · -dark · -strongState/<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-bgFilled Strong · Light Light · Lighter Lighter · Stroke none · Disabled noneBadge
--nu-badge-fgFilled Bg/White · Light Dark · Lighter Strong · Stroke Strong · Disabled Text/Disabled-500Badge
--nu-badge-borderStroke Base · Disabled Stroke/Soft-200 · otherwise noneBadge
--nu-status-badge-bgLight Lighter · Stroke Bg/WhiteStatus badge
--nu-status-badge-fgBase (icon and dot; Light text). Stroke text, and Light disabled text, is Text/Sub-600Status badge
--nu-status-badge-borderStroke Stroke/Soft-200 · Light noneStatus 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.

  • Cell structure and functionReading · 12 minCompleted
  • PhotosynthesisVideo · 18 minIn progress
  • Plant cells check-inAssignmentPending review
  • Respiration quizQuiz · 10 questionsRetake needed

Space header

Category and plan badges sit next to a title. Keep it to one or two; more becomes noise.

Year 9 chemistryProAI tutor on

Counts

A numeric badge shows how many items need attention. Its label gives screen readers the full meaning, and a status region announces changes.

Biology 101 submissions3 to review

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

Badge props
PropTypeDefaultDescription
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.
dotbooleanfalseFigma Type=With Dot: a small dot before the text, in the text colour. Decorative. Ignored when numeric.
leadingIconReactNode—Figma Type=Left Icon. Rendered aria-hidden. Ignored when numeric.
trailingIconReactNode—Figma Type=Right Icon. Rendered aria-hidden. Ignored when numeric.
numericbooleanfalseFigma Number=On: compact, centred, tabular figures. Children are the number; pair it with label.
disabledbooleanfalseFigma Disabled=On: outline and Disabled-500 text for every appearance and colour. Visual only.
labelstring—Text for assistive technology that replaces the visible content, such as “3 to review”. The visible content is then hidden from screen readers.
childrenReactNode—The text, or the number when numeric.
refRef<HTMLSpanElement>—Forwarded to the span.
…restOmit<HTMLAttributes<HTMLSpanElement>, 'color'>—Any other span attribute (id, title, data-*, aria-*…).

StatusBadge props

StatusBadge props
PropTypeDefaultDescription
statusrequired'completed' | 'pending' | 'failed' | 'information' | 'disabled'—Figma Status (StatusBadgeStatus). Sets the colour and the default icon.
childrenrequiredReactNode—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.
dotbooleanfalseFigma With Dot=On: a 6px dot replaces the icon.
iconReactNodethe status iconReplaces the default icon (a Remix icon). Rendered aria-hidden.
refRef<HTMLSpanElement>—Forwarded to the span.
…restHTMLAttributes<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

KeyBehaviour
TabSkips 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 propertyFigma valuesCode
Badge · TypeBasic · With Dot · Left Icon · Right Icondot, leadingIcon, trailingIcon
Badge · 🏵️ StyleFilled · Light · Lighter · Strokeappearance
Badge · 🎨 ColorGray · Blue · Orange · Red · Green · Yellow · Purple · Sky · Pink · Tealcolor
Badge · 📏 SizeSmall (16) · Medium (20) · Large (32), labels wrongsize: sm · md · lg = 16 · 24 · 28
Badge · NumberOff · Onnumeric (+ label, code-only)
Badge · DisabledOff · Ondisabled
Status Badge · StatusCompleted · Pending · Failed · Information · Disabledstatus
Status Badge · StyleLight · Strokeappearance
Status Badge · With DotOff · Ondot; 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.

Don't use Badge for

  • Anything people click, select or remove. Use Tag or Button.
  • Progress through a task. Use Progress.
  • Messages that need explaining. Use a sentence, or a Tooltip on a control.

Do and don't

Upload failed
DoName the status in words. The text works without colour, for everyone.
 
Don'tDon't rely on a coloured dot. A dot alone means nothing to someone who can't see the colour.
LiveDraft
DoUse Light for quiet labels. Light badges sit back in lists; every appearance passes AA, so choose by emphasis.
LiveDraft
Don'tDon't make every label Filled. A row of strong fills competes with the page's main action.
Year 9 chemistryPro
DoKeep one or two badges per item. A badge highlights; several compete with the title.
ProNewLivePopular
Don'tDon't stack badges. Four labels in a row bury the one that matters.

Common mistakes

  • Showing a number with no label, so screen readers announce just “3”.
  • Adding onClick to a badge to make it a filter. Use a toggle Tag.
  • Using disabled to mean “archived”. It's visual only and low contrast; use Status badge status="disabled" with text.
  • Picking a colour for decoration. Colours should map to meaning consistently across a product.