Badge
A count beside a label: unread mail, Applications in a stage, tasks due today. Only numbers go in a badge. A status is a lozenge and a label is a tag.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
import { Badge } from '@/ui/badge';<Badge> 14</Badge>Default
Totals that inform: rows in a View, records in a stage, files on a record. Grey fill, subtle text.
import { Badge } from '@/ui/badge';
export default function BadgeDefault() {
return (
<div className="flex items-center gap-100">
<Badge>{4}</Badge>
<Badge>{14}</Badge>
<Badge>{128}</Badge>
</div>
);
}Important
Counts that ask for action: unread conversations, items in Needs you. Ink fill, white text. Use one per area.
import { Badge } from '@/ui/badge';
export default function BadgeImportant() {
return (
<div className="flex items-center gap-100">
<Badge appearance="important">{3}</Badge>
<Badge appearance="important">{12}</Badge>
</div>
);
}Max
Above max (99 unless you set it) the badge shows 99+. Screen readers still hear the real number.
import { Badge } from '@/ui/badge';
export default function BadgeMax() {
return (
<div className="flex items-center gap-100">
<Badge>{128}</Badge>
<Badge appearance="important" max={9}>
{12}
</Badge>
</div>
);
}In context
Tab count
The badge goes after the label, 6px from it. A tab with nothing in it shows no badge.
import { Badge } from '@/ui/badge';
import { Button } from '@/ui/button';
const tabs = [
{ label: 'Applications', count: 14 },
{ label: 'Contacts', count: 6 },
{ label: 'Files', count: 0 },
];
export default function BadgeTabCount() {
return (
<div className="flex items-center gap-100">
{tabs.map((tab) => (
<Button key={tab.label} variant="ghost">
{tab.label}
<Badge>{tab.count}</Badge>
</Button>
))}
</div>
);
}Navigation count
In a navigation row the badge sits at the end, so counts line up. The row's name includes the count.
import { Badge } from '@/ui/badge';
import { Button } from '@/ui/button';
export default function BadgeNavigationCount() {
return (
<div className="flex w-56 flex-col gap-025">
<Button variant="ghost" className="justify-between" aria-label="Inbox, 3 unread">
Inbox
<Badge appearance="important">{3}</Badge>
</Button>
<Button variant="ghost" className="justify-between" aria-label="Tasks, 8 due">
Tasks
<Badge>{8}</Badge>
</Button>
</div>
);
}Zero
At zero the badge renders nothing. Never show 0, and never a dot without a number.
import { Badge } from '@/ui/badge';
import { Button } from '@/ui/button';
export default function BadgeZero() {
const unread = 0;
return (
<Button variant="ghost">
Inbox
<Badge appearance="important">{unread}</Badge>
</Button>
);
}Avatar group
Several people in one slot: up to three overlapping avatars, then a +N count. The names are one hover away.
Chart
Three plain SVG shapes, each drawn from numbers the page already shows: a line over the period, an ordered split as one bar, and a few months as bars. A chart never disagrees with the figure above it.