On this page
Badges bring the user's attention to something.
- Overview
- Rules and guidance
- Properties
- Styles
- Accessibility
When to use
- To categorise or label an object
- To indicate the stage of a process
- To elevate a specific data item so that it is easier to glance
When not to use
- As a clickable item - Badge is a decorative component, not an interactive one. Depending on context, use CTA Button, CTA Link, Chip, or TextLink instead.
- To communicate multiple pieces of information - each Badge should communicate one clear thing about the object it is attached to. If the object needs more detailed information, use Data Table or Summary List instead.
Anatomy

- Badge
- Label
Basic usage
Edit live code
<Cluster spaceBetween={2}> <ClusterItem><Badge>Tag</Badge></ClusterItem> <ClusterItem><Badge variant="info">Info</Badge></ClusterItem> <ClusterItem><Badge variant="success">Success</Badge></ClusterItem> <ClusterItem><Badge variant="warning">Warning</Badge></ClusterItem> <ClusterItem><Badge variant="error">Error</Badge></ClusterItem> </Cluster>
Variants and features
Tag variant
Use when the Badge classifies or categorises an entity without implying a process state. Tags carry no inherent urgency or semantic direction.
Edit live code
<Cluster spaceBetween={2}> <ClusterItem><Badge>Tag</Badge></ClusterItem> </Cluster>
Status variants
Use when the Badge communicates a process state. The variant should semantically match the state:
- Success for successful, active, or complete states (Done, Complete, Active, Sent)
- Error for failed, deleted, or cancelled states (Failed, Cancelled, Error)
- Warning for flagged, at-risk, or delayed states (At risk, Overdue, Flagged, Waiting)
- Info for in-progress or other neutral states (In progress, Scheduled, New)
The right variant depends on the context, not just the label text. The same word can carry different meanings in different processes - for example, 'Pending' might be a neutral step in one workflow and indicate a delay or problem in another. Always choose the variant that reflects what the state means to the user, not just what it is called.
Edit live code
<Cluster spaceBetween={2}> <ClusterItem><Badge variant="info">Info</Badge></ClusterItem> <ClusterItem><Badge variant="success">Success</Badge></ClusterItem> <ClusterItem><Badge variant="warning">Warning</Badge></ClusterItem> <ClusterItem><Badge variant="error">Error</Badge></ClusterItem> </Cluster>
Variant mapping
Map custom variant names onto the built-in variants with the theme's badge.variants. This is useful when your application uses domain-specific status names that differ from Nebula's defaults - for example, mapping closed to success, or overdue to warning.
Provide the custom variant name as the key and the built-in variant to map it to as the value, then pass the custom name to variant. A name with no mapping falls back to tag.
Edit live code
<ThemeProvider theme={createTheme({ config: { accessibleBadge: true }, badge: { variants: { closed: 'success', overdue: 'warning' } }, })} > <Cluster spaceBetween={2}> <ClusterItem><Badge variant="closed">Closed</Badge></ClusterItem> <ClusterItem><Badge variant="overdue">Overdue</Badge></ClusterItem> <ClusterItem><Badge variant="unmapped">Unmapped</Badge></ClusterItem> </Cluster> </ThemeProvider>
Sizes
The default size (height space.6) is correct for the vast majority of contexts: table cells, summary lists, inline with body text, tab labels, account header.
The large variant (height space.7) is for higher-prominence placements only:
- Beside a page title
- As a primary status indicator in the top right corner of a Card
Edit live code
<Cluster spaceBetween={2}> <ClusterItem> <Badge variant="info">Default</Badge> </ClusterItem> <ClusterItem> <Badge variant="info" size="large">Large</Badge> </ClusterItem> </Cluster>
Legacy badge
The accessible badge is the style shown throughout this page, and the docs site enables it. The library default is still the legacy badge, until teams have migrated. Set the accessibleBadge theme config flag to switch between them.
The legacy badge ignores size, so both badges below render at the same height.
Edit live code
<ThemeProvider theme={createTheme({ config: { accessibleBadge: false } })}> <Cluster spaceBetween={2}> <ClusterItem><Badge>Tag</Badge></ClusterItem> <ClusterItem><Badge variant="info">Info</Badge></ClusterItem> <ClusterItem><Badge variant="success">Success</Badge></ClusterItem> <ClusterItem><Badge variant="warning">Warning</Badge></ClusterItem> <ClusterItem><Badge variant="error">Error</Badge></ClusterItem> <ClusterItem><Badge variant="info" size="large">Large is ignored</Badge></ClusterItem> </Cluster> </ThemeProvider>
Grouping
Tag Badges can be grouped to show multiple attributes of an entity. Status Badges should be used alone.
Use the Cluster layout component for groups of Badges, with the betweenBadges spacing token for horizontal and vertical gaps.
Edit live code
<Stack spaceBetween={2}> <Cluster spaceBetween={2}> <ClusterItem><Badge>UK</Badge></ClusterItem> <ClusterItem><Badge>Electricity</Badge></ClusterItem> <ClusterItem><Badge>EV owner</Badge></ClusterItem> </Cluster> </Stack>
usage
- Badge must use a neutral variant when used for classification or categorisationCopy rule ID for badge.neutral-for-classification
- Badge must use coloured status variants only when communicating process stateCopy rule ID for badge.coloured-for-process-state
- Badge must use the variant that semantically matches the status being communicatedA mismatch may cause the user to take the wrong action, or take no action when they should.Exception: purposefully deprioritising a statusCopy rule ID for badge.semantically-matching-variant
- Badge should only be used to communicate a meaningful status or classification, not as arbitrary decorationCopy rule ID for badge.meaningful-status-only
- Badge large variant should only be used beside headings, or in the top corner of a cardCopy rule ID for badge.large-placement-restriction
- Badge should not be used in form fields, such as within Radio or Checkbox labelsUse plain text instead.Copy rule ID for badge.no-form-fields
- If used in tabsBadge should not be placed in more than one tabUse Badge to draw attention to the most important tab only.Copy rule ID for badge.tabs-max-one
placement
- If used with a headingBadge should be placed to the right of textCopy rule ID for badge.heading-placement
- If used in a cardBadge should be placed in the top right cornerCopy rule ID for badge.card-placement
- If used in a tabBadge should be placed to the right of textCopy rule ID for badge.tab-placement
- Badge should not be used inline inside a paragraph of textCreates visual noise that interrupts reading flow and causes inconsistent line height. Consider restructuring the information instead.Copy rule ID for badge.no-inline-text
content
- Badge label will not wrap onto multiple lines. White-space nowrap is applied by the componentCopy rule ID for badge.will-no-wrap
- Badge variant colour will not be announced by screen readers. Variant colours are decorative and communicated through label text onlyCopy rule ID for badge.will-variant-colour-decorative
- Badge label must be fully self-descriptive and not rely on colour to communicate meaningColour alone does not communicate meaning to all users (WCAG 2.2 SC 1.4.1).Copy rule ID for badge.label.self-descriptive
- Badge label should not be longer than 3 wordsBadge is a compact component. Longer labels indicate the content belongs elsewhere.Copy rule ID for badge.label.max-three-words
- Badge label may show additional information on hover in a tooltip or popoverThis information must not be critical and must not be the only place it appears.Copy rule ID for badge.label.tooltip-non-critical
interaction
- Badge will not receive focus or respond to click or keyboard events. It is a decorative span elementCopy rule ID for badge.will-non-interactive
grouping
- Badge status variant must not show more than one status Badge for the same process on a single entityMultiple statuses for the same process make it unclear which requires the user's attention.Copy rule ID for badge.group.no-same-process-grouping
- Badge group should use the Cluster layout componentCopy rule ID for badge.group.cluster-layout
- Badge group should use the betweenBadges spacing token for horizontal and vertical gapsCopy rule ID for badge.group.spacing-token
- If each Badge is labelled so the process it relates to is clearBadge status variant may appear alongside status Badges for other independent processes on the same entityFor example, account status and payment status are independent. Showing both is legitimate when the context makes each one's meaning clear.Copy rule ID for badge.group.independent-process-grouping
- Badge tag variant may be used in a group to communicate the most important and relevant attributes of an entityCopy rule ID for badge.group.tag-variant-only
Badge
Also accepts all props from HTMLAttributes.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
variant | BadgeVariantName | (string & {}) | 'tag' | No | Colour style of the badge. Accepts a built-in variant or a custom name mapped to one in the theme's `badge.variants`; unknown names fall back to `tag`. The `*-filled` variants and `default` are deprecated aliases. |
size | default large | 'default' | No | Height of the badge. Only applies when the theme has `accessibleBadge` enabled; the legacy badge ignores it. |
Styles
a11y-badge
Defined in Badge/Badge.tsx
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
@when size === 'large' {
height: ${space[7]};
}
@when size !== 'large' {
height: ${space[6]};
}
min-width: ${space[12]};
padding: 0.2rem 1rem;
font-family: ${fonts.body};
font-weight: ${fontWeights.medium};
font-size: ${responsiveFontSizes.body[0]};
line-height: ${lineHeights.body};
white-space: nowrap;
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.body[1]};
}
border-radius: ${radii.large};
color: ${currentVariant.foreground};
background-color: ${currentVariant.background};
legacy-badge
Defined in Badge/LegacyBadge.tsx
font-weight: ${fontWeights.bold};
line-height: ${lineHeights.small};
padding: 0.2rem ${space[2]};
border-radius: ${radii.large};
color: ${currentVariant.foreground};
background-color: ${currentVariant.background};
border: ${borderWidths.standard} solid ${currentVariant.borderColor};
vertical-align: middle;
white-space: nowrap;
font-size: ${responsiveFontSizes.small[0]};
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.small[1]};
}
In brief
Badge is a plain span. A screen reader announces its label text, in the reading order, with no role and no state. It never takes focus.
What Nebula provides
- The label is real text in the page, so it is read in the reading order.
- The variant colours come from theme tokens and are not announced, so the label has to carry the meaning.
Accessible design
Write a label that carries the whole meaning without the colour
A screen reader user hears the label and nothing else, and some sighted users cannot tell the variant colours apart. "Failed" says more than "Error" next to a red fill.
WCAG: 1.4.1 Use of Color 1.3.1 Info and Relationships
Place the Badge after the text it describes in the reading order
The Badge is read where it sits in the markup, whatever its position on screen. A Badge in the corner of a Card is only tied to the Card title by being read next to it.
WCAG: 1.3.2 Meaningful Sequence
If the label comes from data or translation, check it at 200% zoom and 320px wide
Badge never wraps its label, so a longer label overflows instead of reflowing.
WCAG: 1.4.4 Resize Text 1.4.10 Reflow
Accessible development
If a status changes while the user is on the page, announce it outside the Badge
Badge has no role and no live region, so a label that changes in place is silent. Announce the change through a status message such as a Notification.
WCAG: 4.1.3 Status Messages
Don't hide a Badge or rename it with aria-hidden or aria-label
A span has no role, so a name on it is not reliably announced, and hiding it removes the status from the screen reader. Keep the visible label as the only name.
WCAG: 1.3.1 Info and Relationships
Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
|---|---|---|
| Default state | Partially tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What these tests and statuses mean
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
The tests
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
The statuses
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.