# Badge Badges bring the user's attention to something. Section: components Docs: https://nebula.kaluza.com/components/badge --- ## 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](/components/cta-button), [CTA Link](/components/cta-link), [Chip](/components/chip), or [TextLink](/components/text-link) 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](/components/data-table) or [Summary List](/components/summary-list) instead. ## Anatomy 1. **Badge** 2. **Label** ## Basic usage ```tsx Tag Info Success Warning Error ``` ## 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. ```tsx Tag ``` ### 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. ```tsx Info Success Warning Error ``` ### 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`. ```tsx Closed Overdue Unmapped ``` ### 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 ```tsx Default Large ``` ### 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. ```tsx Tag Info Success Warning Error Large is ignored ``` ## Grouping Tag Badges can be grouped to show multiple attributes of an entity. Status Badges should be used alone. Use the [Cluster](/components/layout-components/#cluster) layout component for groups of Badges, with the `betweenBadges` spacing token for horizontal and vertical gaps. ```tsx UK Electricity EV owner ``` ## Rules and guidance ### Usage - **Badge** MUST use a neutral variant when used for classification or categorisation. [id: badge.neutral-for-classification; testability: manual] - **Badge** MUST use coloured status variants only when communicating process state. [id: badge.coloured-for-process-state; testability: manual] - **Badge** MUST use the variant that semantically matches the status being communicated. Rationale: A mismatch may cause the user to take the wrong action, or take no action when they should. Exception: purposefully deprioritising a status. [id: badge.semantically-matching-variant; testability: manual] - **Badge** SHOULD only be used to communicate a meaningful status or classification, not as arbitrary decoration. [id: badge.meaningful-status-only; testability: manual] - **Badge large variant** SHOULD only be used beside headings, or in the top corner of a card. [id: badge.large-placement-restriction; testability: context] - **Badge** SHOULD NOT be used in form fields, such as within Radio or Checkbox labels. Rationale: Use plain text instead. [id: badge.no-form-fields; testability: context] - If used in tabs: **Badge** SHOULD NOT be placed in more than one tab. Rationale: Use Badge to draw attention to the most important tab only. [id: badge.tabs-max-one; testability: context] ### Placement - If used with a heading: **Badge** SHOULD be placed to the right of text. [id: badge.heading-placement; testability: context] - If used in a card: **Badge** SHOULD be placed in the top right corner. [id: badge.card-placement; testability: context] - If used in a tab: **Badge** SHOULD be placed to the right of text. [id: badge.tab-placement; testability: context] - **Badge** SHOULD NOT be used inline inside a paragraph of text. Rationale: Creates visual noise that interrupts reading flow and causes inconsistent line height. Consider restructuring the information instead. [id: badge.no-inline-text; testability: context] ### Content - **Badge label** WILL not wrap onto multiple lines. White-space nowrap is applied by the component. [id: badge.will-no-wrap; testability: auto] - **Badge variant colour** WILL not be announced by screen readers. Variant colours are decorative and communicated through label text only. [id: badge.will-variant-colour-decorative; testability: auto] - **Badge label** MUST be fully self-descriptive and not rely on colour to communicate meaning. Rationale: Colour alone does not communicate meaning to all users (WCAG 2.2 SC 1.4.1). [id: badge.label.self-descriptive; testability: manual] - **Badge label** SHOULD NOT be longer than 3 words. Rationale: Badge is a compact component. Longer labels indicate the content belongs elsewhere. [id: badge.label.max-three-words; testability: auto] - **Badge label** MAY show additional information on hover in a tooltip or popover. Rationale: This information must not be critical and must not be the only place it appears. [id: badge.label.tooltip-non-critical; testability: manual] ### Interaction - **Badge** WILL not receive focus or respond to click or keyboard events. It is a decorative span element. [id: badge.will-non-interactive; testability: auto] ### Grouping - **Badge status variant** MUST NOT show more than one status Badge for the same process on a single entity. Rationale: Multiple statuses for the same process make it unclear which requires the user's attention. [id: badge.group.no-same-process-grouping; testability: context] - **Badge group** SHOULD use the Cluster layout component. [id: badge.group.cluster-layout; testability: context] - **Badge group** SHOULD use the betweenBadges spacing token for horizontal and vertical gaps. [id: badge.group.spacing-token; testability: auto] - If each Badge is labelled so the process it relates to is clear: **Badge status variant** MAY appear alongside status Badges for other independent processes on the same entity. Rationale: For example, account status and payment status are independent. Showing both is legitimate when the context makes each one's meaning clear. [id: badge.group.independent-process-grouping; testability: manual] - **Badge tag variant** MAY be used in a group to communicate the most important and relevant attributes of an entity. [id: badge.group.tag-variant-only; testability: manual] ## Accessibility ### 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](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/meaningful-sequence.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html) [1.4.10 Reflow](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html) ### 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](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) ### 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 each test covers: - 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. What these statuses mean: - 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. 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. ## Properties ### Badge Also accepts all props from `HTMLAttributes`. - `variant` (type `BadgeVariantName | (string & {})`, optional, default `'tag'`). 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` (type `'default' | 'large'`, optional, default `'default'`). Height of the badge. Only applies when the theme has `accessibleBadge` enabled; the legacy badge ignores it. ## Styles Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS. ### a11y-badge ```css 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 ```css 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]}; } ```