role presentation
Badge draft v0.1.0
A short piece of text that qualifies the item next to it: a status, a count, a category.
A badge is a small, read-only label attached to something else: the status of an order (Shipped), the number of unread messages, a category tag. It is text with a tinted or filled background; its intent gives the tint a meaning (success, warning, error, info), its emphasis how loud it is. A badge is never interactive: a removable tag or a filter is a different component.
Web implementation
Root .ds-badge on a <span>. Properties map to attributes: emphasis → data-emphasisintent → data-intentsize → data-size
packages/components/src/components/badge/badge.css
/*
* Badge — vanilla CSS reference implementation.
* Spec: packages/components/specs/components/badge.spec.json
* Tokens: packages/tokens/dist/css/components/badge.css (generated from packages/tokens/src/components/badge.tokens.json)
*
* Rules:
* - Only --badge-* custom properties may appear here. No raw values, no semantic tokens.
* - Emphasis, intent and size are data attributes; a badge has no interaction state.
* - Each emphasis x intent pair sets the private colors, wrapped in :where() so every pair shares one specificity.
*/
.ds-badge {
/* Defaults = emphasis "subtle", intent "neutral", size "medium". */
--_min-height: var(--badge-container-min-height-medium);
--_padding-inline: var(--badge-container-padding-inline-medium);
--_font-family: var(--badge-label-typography-medium-font-family);
--_font-size: var(--badge-label-typography-medium-font-size);
--_font-weight: var(--badge-label-typography-medium-font-weight);
--_line-height: var(--badge-label-typography-medium-line-height);
--_letter-spacing: var(--badge-label-typography-medium-letter-spacing);
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: var(--_min-height);
padding-block: 0;
padding-inline: var(--_padding-inline);
border: var(--badge-container-border-width) solid var(--_border-color);
border-radius: var(--badge-container-border-radius);
background-color: var(--_background);
color: var(--_color);
font-family: var(--_font-family);
font-size: var(--_font-size);
font-weight: var(--_font-weight);
line-height: var(--_line-height);
letter-spacing: var(--_letter-spacing);
white-space: nowrap;
vertical-align: middle;
}
.ds-badge__label {
display: inline-block;
}
.ds-badge[data-size="small"] {
--_min-height: var(--badge-container-min-height-small);
--_padding-inline: var(--badge-container-padding-inline-small);
--_font-family: var(--badge-label-typography-small-font-family);
--_font-size: var(--badge-label-typography-small-font-size);
--_font-weight: var(--badge-label-typography-small-font-weight);
--_line-height: var(--badge-label-typography-small-line-height);
--_letter-spacing: var(--badge-label-typography-small-letter-spacing);
}
/* ---- Emphasis x intent ----------------------------------------------------- */
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="neutral"], :not([data-intent])) { --_background: var(--badge-container-background-subtle-neutral); --_border-color: var(--badge-container-border-color-subtle-neutral); --_color: var(--badge-label-color-subtle-neutral); }
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="brand"]) { --_background: var(--badge-container-background-subtle-brand); --_border-color: var(--badge-container-border-color-subtle-brand); --_color: var(--badge-label-color-subtle-brand); }
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="success"]) { --_background: var(--badge-container-background-subtle-success); --_border-color: var(--badge-container-border-color-subtle-success); --_color: var(--badge-label-color-subtle-success); }
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="warning"]) { --_background: var(--badge-container-background-subtle-warning); --_border-color: var(--badge-container-border-color-subtle-warning); --_color: var(--badge-label-color-subtle-warning); }
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="error"]) { --_background: var(--badge-container-background-subtle-error); --_border-color: var(--badge-container-border-color-subtle-error); --_color: var(--badge-label-color-subtle-error); }
.ds-badge:where([data-emphasis="subtle"], :not([data-emphasis])):where([data-intent="info"]) { --_background: var(--badge-container-background-subtle-info); --_border-color: var(--badge-container-border-color-subtle-info); --_color: var(--badge-label-color-subtle-info); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="neutral"], :not([data-intent])) { --_background: var(--badge-container-background-accent-neutral); --_border-color: var(--badge-container-border-color-accent-neutral); --_color: var(--badge-label-color-accent-neutral); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="brand"]) { --_background: var(--badge-container-background-accent-brand); --_border-color: var(--badge-container-border-color-accent-brand); --_color: var(--badge-label-color-accent-brand); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="success"]) { --_background: var(--badge-container-background-accent-success); --_border-color: var(--badge-container-border-color-accent-success); --_color: var(--badge-label-color-accent-success); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="warning"]) { --_background: var(--badge-container-background-accent-warning); --_border-color: var(--badge-container-border-color-accent-warning); --_color: var(--badge-label-color-accent-warning); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="error"]) { --_background: var(--badge-container-background-accent-error); --_border-color: var(--badge-container-border-color-accent-error); --_color: var(--badge-label-color-accent-error); }
.ds-badge:where([data-emphasis="accent"]):where([data-intent="info"]) { --_background: var(--badge-container-background-accent-info); --_border-color: var(--badge-container-border-color-accent-info); --_color: var(--badge-label-color-accent-info); }
packages/components/src/components/badge/badge.template.mjs
/**
* Badge — HTML template for the web implementation.
* Pure function of the spec properties (packages/components/specs/components/badge.spec.json) -> HTML string.
* A badge is text: a span with no role of its own; its label is what assistive technology reads,
* in the flow of the content it qualifies.
*/
import { escapeHtml, escapeAttr } from '../../lib/template-utils.mjs';
export const meta = { id: 'badge', selector: '.ds-badge' };
export function render(props = {}) {
const { label = 'Badge', emphasis = 'subtle', intent = 'neutral', size = 'medium', state = null } = props;
const attrs = ['class="ds-badge"', `data-emphasis="${escapeAttr(emphasis)}"`, `data-intent="${escapeAttr(intent)}"`, `data-size="${escapeAttr(size)}"`];
if (state && state !== 'default') attrs.push(`data-state="${escapeAttr(state)}"`);
return `<span ${attrs.join(' ')}><span class="ds-badge__label" data-part="label">${escapeHtml(label)}</span></span>`;
}
No other platform implementation recorded yet. Platform token outputs live under Platforms.