Base components
Badge
A short label for a status, category or count in three styles and twelve colors, which eases its colors when its status changes and can carry a small remove button.
Live preview
Loading preview
A grid of the three types in the twelve colors at size md, a row of sizes, badges with a dot, a flag, an avatar and an icon, and a status badge that changes from "In review" (warning) to "Approved" (success). Replay runs that status change again.
Installation
Run this in your app after the one-time setup on the Getting started page.
$ npx @ganisigit/lunia-ds add badgeUsage
The demo above, as code.
'use client';
import * as React from 'react';
import { Badge } from './badge';
import type { BadgeColor, BadgeType } from './badge-variants';
const TYPES: BadgeType[] = ['pill-color', 'badge-color', 'badge-modern'];
const COLORS: BadgeColor[] = ['gray', 'brand', 'error', 'warning', 'success', 'blue'];
export default function BadgeDemo({ replay = 0 }: { replay?: number }) {
const [approved, setApproved] = React.useState(false);
const [tags, setTags] = React.useState(['Design', 'Research']);
React.useEffect(() => {
if (replay === 0) return;
setApproved(false);
setTags(['Design', 'Research']);
const timer = window.setTimeout(() => setApproved(true), 600);
return () => window.clearTimeout(timer);
}, [replay]);
return (
<div className="flex flex-col items-center gap-5">
{TYPES.map((type) => (
<div key={type} className="flex flex-wrap justify-center gap-2">
{COLORS.map((color) => (
<Badge key={color} type={type} color={color} dot={type === 'badge-modern'}>
Label
</Badge>
))}
</div>
))}
<div className="flex flex-wrap items-center justify-center gap-2">
<Badge data-testid="status" color={approved ? 'success' : 'warning'} dot>
{approved ? 'Approved' : 'In review'}
</Badge>
{tags.map((tag) => (
<Badge
key={tag}
type="badge-color"
onRemove={() => setTags((list) => list.filter((t) => t !== tag))}
>
{tag}
</Badge>
))}
</div>
</div>
);
}
API
Parts: root, leading content (dot, country flag, avatar or icon), label, trailing icon, remove button (X close).
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 22, 24 or 28px tall. |
| type | "pill-color" | "badge-color" | "badge-modern" | "pill-color" | Shape and color style: a round pill with a tinted fill, a rounded badge with a tinted fill, or a neutral rounded badge with a colored dot or icon. |
| color | "gray" | "brand" | "error" | "warning" | "success" | "slate" | "sky" | "blue" | "indigo" | "purple" | "pink" | "orange" | "gray" | Color set from the utility color tokens. |
| dot | boolean | false | Shows a colored dot before the label. |
| iconOnly | boolean | false | Shows only the icon passed in the default slot, as a square badge. Needs a label prop for screen readers. |
| label | string | undefined | Accessible name when iconOnly is set, and the name used in the remove button ("Remove Design"). |
| removeLabel | string | "Remove" | Verb for the remove button name. The button is named removeLabel plus the badge text. |
| Event | Description |
|---|---|
| remove | The remove button was pressed. The remove button renders only when this event has a listener. |
| Slot | Description |
|---|---|
| default | Label text, or the icon when iconOnly is set. |
| leading | Flag, avatar or icon before the label (Figma Icon Country, Avatar or Icon leading). |
| trailing | Icon after the label (Figma Icon trailing). |
Motion
Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.
| ID | Trigger | Element | Property | From → to | Timing | Reduced motion |
|---|---|---|---|---|---|---|
| BDG1 | color changes (for example a status moves from warning to success) | root, dot, label | background-color, border-color, color | old color set → new color set | duration-sm (200)ease-out | Swaps at once |
| BDG2 | label text changes | label | opacity | old text 1 -> 0 → new text 0 -> 1 (crossfade) | duration-sm (200)ease-out | Swaps at once |
| BDG3 | hover the remove button | remove button | background-color, icon color | transparent, color set icon → color set hover fill, color set icon hover | duration-sm (200)ease-out | Swaps at once |
| BDG4 | press the remove button | remove button | scale | 1 → scale-press | duration-sm (200)ease-out | Stays still |
| BDG5 | keyboard focus on the remove button | remove button | box-shadow | none → focus ring (2px bg-primary gap, 4px focus-ring) | duration-2xs (120)ease-out | Shows at once |
Accessibility
- The badge is a plain span with no role; it is read as part of the text around it.
- The dot and leading icons are aria-hidden; when the color carries meaning (for example a status), the label says it in words.
- iconOnly badges need the label prop, which becomes aria-label on a span with role img.
- The remove button is a native button named removeLabel plus the badge text, for example "Remove Design".
- The remove button is 16px; a transparent hit area grows the target to 24 x 24px (WCAG 2.5.8).
- Reduced motion: colors and text swap at once and the remove button does not scale.