Badges

Summary

Badges are read-only status pills and count bubbles: tinted semantic variants, a bold "must not miss" fill, the solid -primary count bubble, and overlay/dot forms that pin to an icon button's corner (approvals counts, unread dots). User-managed tags are Labels; toggleable filter pills are Chips.

When to use

ComponentUse it for
Badge (tinted)Status at a glance in tables and lists — healthy, degraded, failed. Never clickable.
Badge -primaryCounts: notifications, pending approvals. Solid fill so it reads at bubble size.
Badge overlay / dotPinning a count or presence marker to an icon button's corner.
LabelFolksonomy the user edits: tags on issues, topics on projects.
ChipFilter toggles above tables and lists (KEV-listed, Suppressed, …).

Examples

Neutral & count

default 3

The neutral base pill, and the solid -primary count bubble.

Tinted variants

active pending failed beta

Subtle semantic fills: -success, -warning, -danger, -info.

Bold

LIVE DEGRADED FAILED BETA ARCHIVED

-bold + a family = the solid "must not miss" fill; bare -bold is solid neutral.

Overlay & dot

.soma-badge-overlay on the host pins the child badge to its corner; .soma-badge-dot is the 8px presence dot. The count lives in the host's aria-label.

Updating a count

Badge text and host aria-label change together in this live demo:

The badge is decoration to screen readers. The demo JS updates the bubble text AND the host's aria-label in one place, and hides the bubble at zero.

Inside solid buttons

Inside .soma-button-primary / -danger a badge flips to a translucent-white tint automatically, with no extra class.

Overlays & dots

.soma-badge-overlay goes on the host (any positioned inline element, typically a .soma-navbar-action icon button) and pins the child badge to its top inline-end corner (mirrored under RTL). The overlaid bubble is slightly smaller than a standalone badge so it doesn't swallow the icon. .soma-badge-dot is the 8px presence form for "there is something" without a number — unread indicators, attention markers. When the state clears, remove (or hide) the badge element; the host keeps its footprint.

Counts and screen readers

The host's aria-label carries the count. An overlaid bubble reading "3" is meaningless out of context to a screen reader ("button, three"), and a dot says nothing at all, so the badge is treated as decoration and the host button owns the full announcement: aria-label="Approvals, 3 pending", aria-label="Notifications, unread". Whenever the count changes, update the badge text and the aria-label together: one function, both writes (see the live example above). Standalone text badges in tables ("failed", "active") need nothing extra: their text is their announcement.

HTML

A status pill; the text is the announcement, no ARIA needed:

<span class="soma-badge soma-badge-success">healthy</span>

A count bubble next to its subject (e.g. in a tab or list row):

Queue <span class="soma-badge soma-badge-primary">7</span>

The bold fill for states that must not be missed:

<span class="soma-badge soma-badge-bold soma-badge-danger">FAILED</span>

A count pinned to an icon button — -overlay on the host, the number in its aria-label:

<button class="soma-navbar-action soma-badge-overlay" aria-label="Approvals, 3 pending">
  <span class="soma-icon soma-icon-check"></span>
  <span class="soma-badge soma-badge-primary">3</span>
</button>

A presence dot with no number, so the meaning lives entirely in the host's label:

<button class="soma-navbar-action soma-badge-overlay" aria-label="Notifications, unread">
  <span class="soma-icon soma-icon-bell"></span>
  <span class="soma-badge soma-badge-dot"></span>
</button>

Inside a solid button the badge flips to translucent white automatically:

<button class="soma-button soma-button-primary">Approvals
  <span class="soma-badge">3</span></button>

CSS classes

ClassEffect
.soma-badgeBase pill (neutral tint).
.soma-badge-primarySolid blue count bubble.
.soma-badge-success / -warning / -danger / -infoTinted semantic variants.
.soma-badge-boldCombines with the semantic variants for a solid "must not miss" fill (bare -bold is solid neutral).
inside .soma-button-primary / -dangerA direct-child badge flips to a translucent-white tint automatically (no extra class).
.soma-badge-overlayOn the HOST element: pins a child badge to its top inline-end corner (RTL-aware), sized down slightly.
.soma-badge-dot8px presence dot, no text — the meaning lives in the host's aria-label.

JavaScript

None shipped — badges are CSS-only. The one stateful pattern is keeping a count's two representations in sync: whenever it changes, write the badge text and the host's aria-label from the same function.

function setApprovalCount(n) {
  const host = document.getElementById('approvals-button');
  const bubble = host.querySelector('.soma-badge');
  bubble.textContent = String(n);
  bubble.style.display = n === 0 ? 'none' : '';
  host.setAttribute('aria-label',
    n === 0 ? 'Approvals, none pending' : `Approvals, ${n} pending`);
}

The live example above, exactly as this page wires it:

const host = document.getElementById('badge-live');
const bubble = document.getElementById('badge-live-count');
let approvals = 3;

function render() {
  bubble.textContent = String(approvals);
  bubble.style.display = approvals === 0 ? 'none' : '';
  host.setAttribute('aria-label',
    approvals === 0 ? 'Approvals, none pending' : `Approvals, ${approvals} pending`);
}

document.getElementById('badge-inc').addEventListener('click', () => {
  approvals += 1;
  render();
});
document.getElementById('badge-clear').addEventListener('click', () => {
  approvals = 0;
  render();
});