Badge
A small label for a status or a count.
Preview
import { Badge } from '@kerf-js/veneer'export function Dot() { return ( <> <Badge tone="positive" dot > Live </Badge> <Badge tone="info" dot > Building </Badge> <Badge tone="critical" dot > Failed </Badge> <Badge tone="neutral" dot > Paused </Badge> </> )}Installation
npm install @kerf-js/veneerTry it
Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.
import { Badge } from '@kerf-js/veneer'<Badge tone="positive" dot>Live</Badge>Usage
import { Badge } from '@kerf-js/veneer'<Badge>Live</Badge>Variants
soft by default.
import { Badge } from '@kerf-js/veneer'export function Emphasis() { return ( <> <Badge tone="positive">Status</Badge> <Badge tone="positive" emphasis="solid" > Status </Badge> <Badge tone="positive" emphasis="outline" > Status </Badge> </> )}Tones
Use tone to colour a badge by what it means.
import { Badge } from '@kerf-js/veneer'export function Tones() { return ( <> {TONES.map((tone) => ( <Badge key={tone} tone={tone} > Status </Badge> ))} </> )}type Status = | 'Live' | 'Building' | 'Failed' | 'Paused'const TONES = [ 'neutral', 'accent', 'positive', 'caution', 'critical', 'info',] as constCounts
count makes it round. srText says what is being counted.
import { Badge } from '@kerf-js/veneer'export function Count() { return ( <> <Badge count tone="accent" srText="unread comments" > 3 </Badge> <Badge count tone="critical" emphasis="solid" srText="failed deploys" > 12 </Badge> <Badge count emphasis="outline" srText="open reviews" > 99+ </Badge> </> )}Status in a list
Use dot with a tone to show each row's status at a glance.
import { Badge, Row, Stack, Text } from '@kerf-js/veneer'export function ProjectList() { return ( <Stack gap="related" divided > {PROJECTS.slice(2, 7).map((p) => ( <Row key={p.id} justify="between" gap="group" > <Stack gap="hair"> <Text>{p.name}</Text> <Text role="caption" subtle > {p.owner} </Text> </Stack> <Badge tone={TONE_OF[p.status]} dot > {p.status} </Badge> </Row> ))} </Stack> )}type Status = | 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string name: string owner: string region: string status: Status deploys: number uptime: number latency: number cost: number updated: string}[] = [ { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31', }, { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31', }, { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30', }, { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29', }, { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29', }, { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27', }, { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22', }, { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31', }, { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28', }, { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30', },]const TONE_OF: Record< Status, 'positive' | 'info' | 'critical' | 'neutral'> = { Live: 'positive', Building: 'info', Failed: 'critical', Paused: 'neutral',}When to use
Use it for
- Saying what state something is in
- A count on a button or a heading
Reach for something else
- Tag, when people wrote the label themselves
- Chip, when pressing it should do something
- LiveStatus, when it shows a live connection
Accessibility
- The words carry the status, so it never depends on colour.
- A count alone means little to a screen reader: use
srText.
Theming
Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works
| Key | Own keys | States |
|---|---|---|
parts.badge | — | hover, focus, press, active, open, disabled |
veneer({ colors: { brand: '#e8590c', ink: '#1b1b1f' }, parts: { badge: { radius: 'pill', case: 'uppercase', tracking: 0.5 }, },})API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
tone | Tone | — | What it means. |
emphasis | "solid" | "soft" | "outline" | — | soft, solid or outline. |
count | boolean | false | Round, for a number. |
dot | boolean | false | A status dot before the label. |
srText | string | — | Extra words for screen readers, like "unread comments". |
children | unknown | — | What it contains. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="badge"] [data-part="sr-only"]
sr-only
RelatedTagChipLive status