Badge
A small label for status, category or count — pill or square, tinted, outlined or modern, with optional icon, dot, avatar or dismiss button.
Examples
Colours
Eight colour modifiers on the default pill: gray (default), badge_brand, badge_error, badge_warning, badge_success, badge_blue, badge_orange and badge_dark.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge">Label</span>
<span class="kompan-badge kompan-badge_brand">Brand</span>
<span class="kompan-badge kompan-badge_error">Error</span>
<span class="kompan-badge kompan-badge_warning">Warning</span>
<span class="kompan-badge kompan-badge_success">Success</span>
<span class="kompan-badge kompan-badge_blue">Blue</span>
<span class="kompan-badge kompan-badge_orange">Orange</span>
<span class="kompan-badge kompan-badge_dark">Dark</span>
</div>Types
Four shapes: the default pill, badge_outline (border only), badge_square (rounded corners) and badge_square + badge_modern (white surface, neutral text, soft shadow, coloured icon).
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_brand"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Pill</span>
<span class="kompan-badge kompan-badge_brand kompan-badge_outline"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Outline</span>
<span class="kompan-badge kompan-badge_brand kompan-badge_square"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Square</span>
<span class="kompan-badge kompan-badge_brand kompan-badge_square kompan-badge_modern"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Modern</span>
</div>Sizes
badge_sm (default, 22px), badge_md (24px) and badge_lg (28px). md and lg share the sm text size; lg adds vertical padding.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_sm kompan-badge_success"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Small</span>
<span class="kompan-badge kompan-badge_md kompan-badge_success"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Medium</span>
<span class="kompan-badge kompan-badge_lg kompan-badge_success"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Large</span>
</div>Icons and dot
Put a 12px svg.badge__icon before or after the label; a leading span.badge__dot shows a status disc. The outer padding tightens automatically on the icon side.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_blue"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9.5v-7M2.5 6 6 2.5 9.5 6"/></svg>Leading</span>
<span class="kompan-badge kompan-badge_blue">Trailing<svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6h7M6 2.5 9.5 6 6 9.5"/></svg></span>
<span class="kompan-badge kompan-badge_success"><span class="kompan-badge__dot" aria-hidden="true"></span>Active</span>
<span class="kompan-badge"><span class="kompan-badge__dot" aria-hidden="true"></span>Offline</span>
</div>Avatar and flag
A 16px round image in badge__avatar — a person, or a country flag for locale badges.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_lg"><span class="kompan-badge__avatar"><img src="https://images.unsplash.com/photo-1600603406200-5b2a104684ac?w=64&h=64&fit=crop&crop=faces&q=80" alt="" /></span>Mette Hansen</span>
<span class="kompan-badge kompan-badge_lg kompan-badge_brand"><span class="kompan-badge__avatar"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="#d80027"/><path d="M6.5 0.1v6.4H0.1a8 8 0 0 0 0 3h6.4v6.4a8 8 0 0 0 3 0V9.5h6.4a8 8 0 0 0 0-3H9.5V0.1a8 8 0 0 0-3 0Z" fill="#f0f0f0"/></svg></span>Denmark</span>
</div>Dismissible
A trailing button.badge__close removes the badge. Give it an aria-label that names what is removed.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_md">Filter: Playgrounds<button class="kompan-badge__close" type="button" aria-label="Remove filter Playgrounds"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
<span class="kompan-badge kompan-badge_md kompan-badge_brand kompan-badge_square">Brand<button class="kompan-badge__close" type="button" aria-label="Remove Brand"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
</div>Icon only
badge_icon-only pads equally on all sides. The icon carries no text, so add an accessible name.
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
<span class="kompan-badge kompan-badge_icon-only kompan-badge_blue" role="img" aria-label="Add"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 2.5v7M2.5 6h7"/></svg></span>
<span class="kompan-badge kompan-badge_md kompan-badge_icon-only kompan-badge_blue" role="img" aria-label="Add"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 2.5v7M2.5 6h7"/></svg></span>
<span class="kompan-badge kompan-badge_lg kompan-badge_icon-only kompan-badge_blue" role="img" aria-label="Add"><svg class="kompan-badge__icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 2.5v7M2.5 6h7"/></svg></span>
</div>Accessibility
- Badges are text, not controls
A badge is a plain span with visible text; it needs no role. Do not make the whole badge clickable — put actions in a real button (see the dismissible pattern) so keyboard users can reach them.
- Colour must not carry the only meaning
The colour modifiers and badge__dot convey state through colour alone. Make sure the label itself says what the state is ("Active", "Error"), or add visually-hidden text (class sr-only) next to the dot.
- Decorative icons and avatars
badge__icon, badge__dot and the flag/portrait in badge__avatar are decorative when the label already names the thing. Mark svgs aria-hidden="true" and use alt="" on the image. Only the icon-only badge needs its own name: give it role="img" and an aria-label.
- Dismiss button needs a name
button.badge__close shows only an × icon. Set aria-label="Remove {label}" so the action is announced with its target, and keep the visible focus ring.
- Contrast
Text uses the 700 step on the 50 tint (warning: 700 on 100), which meets AA for the 12–14px medium-weight label. Icons at the 500 step on the tint are graphics — keep them decorative, or lift them to the 600 step (outline/square types) when they carry meaning.
Stylesheet
Shipped as @wah-kompan/kompan-ui/components/badge.css. Every value is a token reference, so it themes with the rest of the system rather than being restyled.
.kompan-badge {
/* Size ramp — sm is the Figma default */
--kompan-badge-py: var(--spacing-xxs);
--kompan-badge-px: var(--spacing-md);
--kompan-badge-px-icon: var(--spacing-sm);
--kompan-badge-px-media: 0.1875rem;
--kompan-badge-gap: var(--spacing-xs);
--kompan-badge-radius: var(--radius-full);
--kompan-badge-square-radius: var(--radius-sm);
/* Colour scale — gray is the Figma default */
--kompan-badge-tint: var(--color-utility-gray-50);
--kompan-badge-tint-border: var(--color-utility-gray-200);
--kompan-badge-icon: var(--color-utility-gray-500);
--kompan-badge-strong: var(--color-utility-gray-600);
--kompan-badge-text: var(--color-utility-gray-700);
--kompan-badge-modern-icon: var(--color-fg-quaternary);
/* Resolved per type (Pill color is the Figma default) */
--kompan-badge-bg: var(--kompan-badge-tint);
--kompan-badge-border: var(--kompan-badge-tint-border);
--kompan-badge-fg: var(--kompan-badge-text);
--kompan-badge-icon-fg: var(--kompan-badge-icon);
display: inline-flex;
align-items: center;
gap: var(--kompan-badge-gap);
padding: var(--kompan-badge-py) var(--kompan-badge-px);
border-radius: var(--kompan-badge-radius);
background: var(--kompan-badge-bg);
box-shadow: inset 0 0 0 var(--stroke-sm) var(--kompan-badge-border);
color: var(--kompan-badge-fg);
font-family: var(--type-font-family-body);
font-size: var(--type-font-size-xs);
font-weight: var(--type-font-weight-medium);
line-height: var(--type-line-height-xs);
white-space: nowrap;
vertical-align: middle;
}
/* Sizes */
.kompan-badge_sm {
--kompan-badge-py: var(--spacing-xxs);
--kompan-badge-px: var(--spacing-md);
--kompan-badge-px-icon: var(--spacing-sm);
--kompan-badge-px-media: 0.1875rem;
--kompan-badge-square-radius: var(--radius-sm);
font-size: var(--type-font-size-xs);
line-height: var(--type-line-height-xs);
}
.kompan-badge_md {
--kompan-badge-py: var(--spacing-xxs);
--kompan-badge-px: 0.625rem;
--kompan-badge-px-icon: var(--spacing-md);
--kompan-badge-px-media: var(--spacing-xs);
--kompan-badge-square-radius: var(--radius-sm);
font-size: var(--type-font-size-sm);
line-height: var(--type-line-height-sm);
}
.kompan-badge_lg {
--kompan-badge-py: var(--spacing-xs);
--kompan-badge-px: var(--spacing-lg);
--kompan-badge-px-icon: 0.625rem;
--kompan-badge-px-media: var(--spacing-sm);
--kompan-badge-gap: var(--spacing-xs);
--kompan-badge-square-radius: var(--radius-md);
font-size: var(--type-font-size-sm);
line-height: var(--type-line-height-sm);
}
/* Types */
.kompan-badge_outline {
--kompan-badge-bg: transparent;
--kompan-badge-border: var(--kompan-badge-strong);
--kompan-badge-icon-fg: var(--kompan-badge-strong);
}
.kompan-badge_square {
--kompan-badge-radius: var(--kompan-badge-square-radius);
--kompan-badge-icon-fg: var(--kompan-badge-strong);
}
.kompan-badge_square { --kompan-badge-px: var(--spacing-sm); --kompan-badge-px-icon: var(--spacing-xs); }
.kompan-badge_md.kompan-badge_square { --kompan-badge-px: var(--spacing-md); --kompan-badge-px-icon: var(--spacing-sm); }
.kompan-badge_lg.kompan-badge_square { --kompan-badge-px: 0.625rem; --kompan-badge-px-icon: var(--spacing-md); }
.kompan-badge_modern {
--kompan-badge-bg: var(--color-bg-primary);
--kompan-badge-border: var(--color-border-primary);
--kompan-badge-fg: var(--color-text-secondary);
--kompan-badge-icon-fg: var(--kompan-badge-modern-icon);
box-shadow:
inset 0 0 0 var(--stroke-sm) var(--kompan-badge-border),
0 1px 2px 0 var(--color-utility-alpha-black-5);
}
/* Colours */
.kompan-badge_brand {
--kompan-badge-tint: var(--color-utility-brand-50);
--kompan-badge-tint-border: var(--color-utility-brand-200);
--kompan-badge-icon: var(--color-utility-brand-500);
--kompan-badge-strong: var(--color-utility-brand-600);
--kompan-badge-text: var(--color-utility-brand-700);
--kompan-badge-modern-icon: var(--color-utility-brand-600);
}
.kompan-badge_error {
--kompan-badge-tint: var(--color-utility-error-50);
--kompan-badge-tint-border: var(--color-utility-error-200);
--kompan-badge-icon: var(--color-utility-error-500);
--kompan-badge-strong: var(--color-utility-error-600);
--kompan-badge-text: var(--color-utility-error-700);
--kompan-badge-modern-icon: var(--color-utility-error-600);
}
.kompan-badge_warning {
--kompan-badge-tint: var(--color-utility-warning-100);
--kompan-badge-tint-border: var(--color-utility-warning-600);
--kompan-badge-icon: var(--color-utility-warning-600);
--kompan-badge-strong: var(--color-utility-warning-600);
--kompan-badge-text: var(--color-utility-warning-700);
--kompan-badge-modern-icon: var(--color-utility-warning-600);
}
.kompan-badge_success {
--kompan-badge-tint: var(--color-utility-success-50);
--kompan-badge-tint-border: var(--color-utility-success-200);
--kompan-badge-icon: var(--color-utility-success-500);
--kompan-badge-strong: var(--color-utility-success-600);
--kompan-badge-text: var(--color-utility-success-700);
--kompan-badge-modern-icon: var(--color-utility-success-600);
}
.kompan-badge_blue {
--kompan-badge-tint: var(--color-utility-blue-50);
--kompan-badge-tint-border: var(--color-utility-blue-200);
--kompan-badge-icon: var(--color-utility-blue-500);
--kompan-badge-strong: var(--color-utility-blue-600);
--kompan-badge-text: var(--color-utility-blue-700);
--kompan-badge-modern-icon: var(--color-utility-blue-500);
}
.kompan-badge_orange {
--kompan-badge-tint: var(--color-utility-orange-50);
--kompan-badge-tint-border: var(--color-utility-orange-200);
--kompan-badge-icon: var(--color-utility-orange-500);
--kompan-badge-strong: var(--color-utility-orange-600);
--kompan-badge-text: var(--color-utility-orange-700);
--kompan-badge-modern-icon: var(--color-utility-orange-500);
}
/* Figma: blue and orange icons stay at the 500 step in every type */
.kompan-badge_blue.kompan-badge_outline, .kompan-badge_blue.kompan-badge_square,
.kompan-badge_orange.kompan-badge_outline, .kompan-badge_orange.kompan-badge_square {
--kompan-badge-icon-fg: var(--kompan-badge-icon);
}
.kompan-badge_dark {
--kompan-badge-tint: var(--color-utility-alpha-black-100);
--kompan-badge-tint-border: var(--color-utility-gray-800);
--kompan-badge-icon: var(--color-utility-alpha-white-100);
--kompan-badge-strong: var(--color-utility-gray-800);
--kompan-badge-text: var(--color-utility-alpha-white-100);
--kompan-badge-modern-icon: var(--color-utility-alpha-white-100);
}
/* Slots — the outer edge next to media tightens to the icon padding */
.kompan-badge__icon,
.kompan-badge__dot,
.kompan-badge__avatar,
.kompan-badge__close {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.kompan-badge__icon { width: 0.75rem; height: 0.75rem; color: var(--kompan-badge-icon-fg); }
.kompan-badge__icon:first-child { margin-left: calc(var(--kompan-badge-px-icon) - var(--kompan-badge-px)); }
.kompan-badge__icon:last-child { margin-right: calc(var(--kompan-badge-px-icon) - var(--kompan-badge-px)); }
.kompan-badge__dot {
width: 0.5rem;
height: 0.5rem;
margin-left: calc(var(--kompan-badge-px-icon) - var(--kompan-badge-px));
}
.kompan-badge__dot::before {
content: '';
width: 0.375rem;
height: 0.375rem;
border-radius: var(--radius-full);
background: var(--kompan-badge-icon);
}
.kompan-badge__avatar {
width: 1rem;
height: 1rem;
margin-left: calc(var(--kompan-badge-px-media) - var(--kompan-badge-px));
border-radius: var(--radius-full);
overflow: hidden;
}
.kompan-badge__avatar img, .kompan-badge__avatar svg { width: 100%; height: 100%; object-fit: cover; }
.kompan-badge__close {
width: 1rem;
height: 1rem;
margin-right: calc(var(--kompan-badge-px-media) - var(--kompan-badge-px));
padding: var(--spacing-xxs);
border: 0;
border-radius: var(--radius-full);
background: transparent;
color: var(--kompan-badge-icon-fg);
cursor: pointer;
}
.kompan-badge__close svg { width: 100%; height: 100%; }
.kompan-badge__close:hover { background: var(--kompan-badge-tint-border); }
.kompan-badge__close:focus-visible { outline: var(--stroke-md) solid var(--kompan-badge-strong); outline-offset: 1px; }
.kompan-badge_square .kompan-badge__close { border-radius: var(--radius-xs); }
/* Icon only — equal padding on every side */
.kompan-badge_icon-only { padding: 0.3125rem; }
.kompan-badge_md.kompan-badge_icon-only { padding: var(--spacing-sm); }
.kompan-badge_lg.kompan-badge_icon-only { padding: var(--spacing-md); }
.kompan-badge_icon-only .kompan-badge__icon { margin: 0; }