V2 componentsUpdated Oct 14, 2026
Badge
A small label for metadata: durations, counts, sources, status, tags. Static by default, and it must look static: the default subtle variant is a borderless wash, because an edge on a control surface is what reads as "press me". Only a badge that does something takes variant="outline" and a render element (render={<button type="button" onClick={…} />} or render={<a href="…" />}), which adds hover, press and a focus ring. Use tone for meaning (status tones) or grouping (category tones), never a className tint. For a dropdown on a badge, use Select with size="badge".
Source: packages/ui/src/components/badge
import { Badge } from '@highlight/ui'Change the props; the preview and the snippet update together.
<Badge left={<Clock />}>45 min</Badge>Icons and images in the left slot are sized for you. dot adds a status mark in the tone.
import { Calendar } from 'lucide-react'import { Badge } from '@highlight/ui' export default function BadgeBasic() { return ( <div className="flex flex-wrap items-center justify-center gap-2"> <Badge>Default</Badge> <Badge left={<Calendar />}>Tomorrow, 9:00</Badge> <Badge tone="brand">Brand</Badge> <Badge tone="success" dot> Done </Badge> <Badge tone="danger" dot> Blocked </Badge> </div> )}A static badge has no edge and no hover. outline + render={<button />} reads as a control. onClear adds a remove button (static badges only).
import { useState } from 'react'import { Badge } from '@highlight/ui'import { ChevronDown, FileText } from 'lucide-react' export default function BadgeInteractive() { const [people, setPeople] = useState(['Lin', 'Josh', 'Vaun']) return ( <div className="flex flex-col items-center gap-4"> <div className="flex flex-wrap items-center gap-2"> <Badge left={<FileText />}>Static: start of document</Badge> <Badge variant="outline" left={<FileText />} right={<ChevronDown />} render={<button type="button" />}> Pressable: start of document </Badge> </div> <div className="flex flex-wrap items-center gap-2"> {people.map((name) => ( <Badge key={name} onClear={() => setPeople((all) => all.filter((n) => n !== name))} clearLabel={`Remove ${name}`} > {name} </Badge> ))} </div> </div> )}subtle for most labels; solid only for a count or status that must be seen at a glance.
import { BADGE_TONES, Badge } from '@highlight/ui' // The status tones: everything before the categories.const TONES = BADGE_TONES.slice(0, 6) export default function BadgeVariants() { return ( <div className="grid gap-2"> {(['subtle', 'solid'] as const).map((variant) => ( <div key={variant} className="flex flex-wrap items-center gap-2"> {TONES.map((tone) => ( <Badge key={tone} variant={variant} tone={tone} className="capitalize"> {tone} </Badge> ))} </div> ))} </div> )}No meaning, only grouping: labels, projects, people.
import { BADGE_TONES, Badge } from '@highlight/ui' const CATEGORIES = BADGE_TONES.slice(6) export default function BadgeCategories() { return ( <div className="flex flex-wrap items-center justify-center gap-2"> {CATEGORIES.map((tone) => ( <Badge key={tone} tone={tone} className="capitalize"> {tone} </Badge> ))} </div> )}16 to 32px. md (24) is the default.
import { Badge } from '@highlight/ui'import { Clock } from 'lucide-react' const SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const export default function BadgeSizes() { return ( <div className="flex flex-wrap items-center justify-center gap-2"> {SIZES.map((size) => ( <Badge key={size} size={size} left={<Clock />}> {size} </Badge> ))} </div> )}An img child is rounded and sized like an icon.
import { Badge } from '@highlight/ui' export default function BadgeAvatar() { return ( <div className="flex flex-wrap items-center justify-center gap-2"> <Badge> <img src="https://api.dicebear.com/9.x/initials/svg?seed=Lin" alt="" /> Lin </Badge> <Badge> <img src="https://api.dicebear.com/9.x/initials/svg?seed=Vaun" alt="" /> Vaun </Badge> </div> )}The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.
Badge- The pill: a span, or a button / a when given onClick / href. left and right take an icon or image beside the label.
Props added on top of the underlying element or Base UI part.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'subtle' | 'outline' | 'solid' | — | subtle (default) is static; outline is for a badge that is pressed; solid for loud counts. |
tone | BadgeTone (BADGE_TONES) | — | Status tones carry meaning; the category tones only tell groups apart. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | — | 16, 20, 24, 28, 32px tall. |
shape | 'pill' | 'squircle' | — | pill (default) or a squircle at half the height. |
dot | boolean | — | A status dot before the label. |
left / right | ReactNode | — | Leading and trailing slots. |
onClick / href | handler / string | — | Renders a <button> / <a> and turns on interactive states. |
onClear / clearLabel | handler / string | — | Trailing remove button and its accessible name. |