Badge
Small non-interactive tag. Three variants ship in the kit: Badge (solid label — default / secondary / outline / destructive / verified), StatusBadge (transaction/entity status with a filled icon, soft-tinted by default or outline with the tone on the icon: created / pending / succeeded / declined / refunded / inactive / archived / paused / failed / warning), and BadgeNumber (pill count — 8 / 12 / 99+). For anything clickable use Button.
Interactive playground
Toggle the props in the panel on the right and see the Badge re-render in real time. The code block below reflects the exact JSX with the values you picked, ready to copy into your prototype.
<Badge variant="default">"Approved"</Badge>Anatomy
- 1Badge (solid label)
The design system's Badge: inline-flex rounded-md border px-2 py-px text-xs font-semibold. Every variant is 20px tall, as in Figma: a 16px line, 1px of padding and 1px of border above and below (the solid variants carry border-transparent). The height is not fixed, so a Badge given its own padding still grows. Icons composed as children auto-size to 12px via [&>svg]:size-3. Variants: default (primary) / secondary / outline / destructive / verified (blue-500).
- 2StatusBadge (status pill)
Soft-tinted by default (color/15 bg + color/foreground text), or variant="outline" (outline Badge, the tone on the icon alone), with a filled Phosphor icon and a canonical label per status. Pass status='created|pending|succeeded|declined|refunded|inactive|archived|paused|failed|warning'; override children to change the label.
- 3BadgeNumber (count pill)
h-5 min-w-5 rounded-full for counts. Same variants as Badge. Use for notification bells, inbox counters, unread markers.
Variants
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="verified">
<SealCheck weight="fill" />
Verified
</Badge>With icons
<Badge>
<CheckCircle weight="light" />
Approved
</Badge>
<Badge variant="secondary">
Next
<ArrowRight weight="light" />
</Badge>Status map (StatusBadge)
Each status ships with a token color + filled Phosphor icon + default label. Override the label via children when the domain wording differs ('Approved' vs 'Succeeded'). The payment states' colours are also the ones a chart of payment states uses (see the Payment states recipe on /components/chart): Created --status-info, Pending warning, Succeeded success, Declined destructive, Refunded --status-refunded. An Error state has no preset: tone destructive + WarningCircle + label 'Error'.
Custom mode (any domain)
When the prototype is not payments (Reports, Notifications, Audit logs, Users, etc.), skip the preset and pass tone + icon + label instead. The tone maps to the same semantic palette the presets use (success / warning / destructive / info / neutral), so the badge still reads as a Yuno badge, just carrying a label the payments vocabulary does not cover. Preset and custom mode are mutually exclusive at the type level.
{/* Custom mode: tone + icon + label instead of a preset status. */}
<StatusBadge tone="neutral" icon={FileDashed} label="Draft" />
<StatusBadge tone="success" icon={CloudArrowUp} label="Published" />
<StatusBadge tone="info" icon={CalendarBlank} label="Scheduled" />
<StatusBadge tone="warning" icon={EnvelopeSimpleOpen} label="Unread" />Outline (several states on one surface)
variant="outline" draws the same status on the outline Badge with the tone on the icon alone. Use it where one surface carries several states at once (a queue, a brief, a list of cards): tinted pills side by side read as a row of coloured blocks. Payments and Operations tables keep the soft default, which is how production draws payment states. Same height, icon and type as soft, so swapping never moves a row vertically (it is 2px wider, the border). A className with another text-* colour recolours the text only; the icon keeps its tone. In light, the yellow icon (pending, paused, warning) sits at 2.9:1, under the 3:1 a non-text mark should reach; the word still carries the state, and the palette fix is its own change.
import { CalendarBlank } from "@phosphor-icons/react/dist/ssr";
import { StatusBadge } from "@/components/ui/badge";
{/* Soft (default): the tone tints the badge. Payments and Operations
tables, where production draws payment states this way. */}
<StatusBadge status="succeeded" />
<StatusBadge status="pending" />
<StatusBadge status="declined" />
<StatusBadge status="refunded" />
<StatusBadge tone="info" icon={CalendarBlank} label="Scheduled" />
{/* Outline: the tone on the icon alone. A surface carrying several
states at once (a queue, a brief, a list of cards). */}
<StatusBadge variant="outline" status="succeeded" />
<StatusBadge variant="outline" status="pending" />
<StatusBadge variant="outline" status="declined" />
<StatusBadge variant="outline" status="refunded" />
<StatusBadge variant="outline" tone="info" icon={CalendarBlank} label="Scheduled" />Long labels
A badge is read as one object, so it never wraps and never runs past maxChars (default 20). Past the cap the text is clipped with a real ellipsis and the full string moves into a Tooltip — which exists ONLY when something was actually cut, because a tooltip that repeats what is already on screen teaches people to ignore tooltips. Icons composed as children are never counted or clipped. The cap is about LENGTH, not fit: a badge carrying a sentence is wrong at any width, and should read the same wherever it appears.
{/* Under the cap: rendered as-is, no wrapper, no tooltip. */}
<Badge variant="secondary">Not published</Badge>
<Badge variant="secondary">Network tokens off</Badge>
{/* Past it: clipped with a real ellipsis, full text in a Tooltip. */}
<Badge variant="secondary">Requires manual review before capture</Badge>
<StatusBadge status="declined">Insufficient funds on the issuing account</StatusBadge>
{/* Raise the cap where a surface genuinely needs a longer label. */}
<Badge variant="secondary" maxChars={40}>Requires manual review before capture</Badge>Number
<BadgeNumber>8</BadgeNumber>
<BadgeNumber variant="secondary">12</BadgeNumber>
<BadgeNumber variant="outline">3</BadgeNumber>
<BadgeNumber variant="destructive">99+</BadgeNumber>Recipes
Ready-to-copy compositions covering the most common Yuno usages of this atom.
The canonical usage: StatusBadge inside a table cell to communicate a payment/transaction state at a glance. Use the domain wording via children when it differs from the default label.
| Transaction | Amount | Status |
|---|---|---|
| tx_a1b2 | $ 145.20 | Approved |
| tx_c3d4 | $ 89.00 | Pending |
| tx_e5f6 | $ 320.50 | Declined |
<Table>
<TableHeader>
<TableRow>
<TableHead>Transaction</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>tx_a1b2</TableCell>
<TableCell>$ 145.20</TableCell>
<TableCell><StatusBadge status="succeeded">Approved</StatusBadge></TableCell>
</TableRow>
<TableRow>
<TableCell>tx_c3d4</TableCell>
<TableCell>$ 89.00</TableCell>
<TableCell><StatusBadge status="pending" /></TableCell>
</TableRow>
<TableRow>
<TableCell>tx_e5f6</TableCell>
<TableCell>$ 320.50</TableCell>
<TableCell><StatusBadge status="declined" /></TableCell>
</TableRow>
</TableBody>
</Table>BadgeNumber pinned top-right of a bell/inbox IconButton via relative + absolute positioning. Cap at 99+ so it does not push the icon.
<div className="relative inline-block">
<Button variant="ghost" size="icon" aria-label="Notifications">
<BellSimple weight="light" />
</Button>
<BadgeNumber
variant="destructive"
className="absolute -right-1 -top-1 pointer-events-none"
>
8
</BadgeNumber>
</div>Solid Badge (outline / secondary) placed after a title to categorize a resource (Rule type, Plan tier, Environment). Keep to one tag per title so it does not become noise.
High-value BIN block
Fraud rule<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-foreground">High-value BIN block</h3>
<Badge variant="outline">Fraud rule</Badge>
</div>verified variant with a leading SealCheck (fill) to mark an account, merchant or method as verified. Reserved for trust signals — never for status.
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground">Acme Merchants Ltd.</span>
<Badge variant="verified">
<SealCheck weight="fill" />
Verified
</Badge>
</div>Import
Copy this import line at the top of the file where you compose this atom.
import { Badge, BadgeNumber, StatusBadge } from "@/components/ui/badge";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| Badge.variant | "default" | "secondary" | "outline" | "destructive" | "verified" | "default" | Solid label variants. Default = primary; verified = blue-500 (trust flag). |
| Badge.className | string | — | Merged. Icons composed as children auto-size to 12px via [&>svg]:size-3. |
| StatusBadge.status | "created" | "pending" | "succeeded" | "declined" | "refunded" | "inactive" | "archived" | "paused" | "failed" | "warning" | — | Required. Picks the color token + filled Phosphor icon + default label. |
| StatusBadge.variant | "soft" | "outline" | "soft" | Default "soft": the tone tints the badge. "outline": outline Badge, tone on the icon alone, for surfaces with several states at once. Same 20px height either way. |
| StatusBadge.children | React.ReactNode | — | Optional label override. Use when domain wording differs from the default (Approved vs Succeeded). |
| BadgeNumber.variant | "default" | "secondary" | "outline" | "destructive" | "default" | Same palette as Badge, without verified (counts do not signal trust). |
| BadgeNumber.children | React.ReactNode | — | The count. Cap display at 99+ so the pill does not push its neighbor. |
| badgeVariants / badgeNumberVariants / statusConfig | helpers | — | Exported for advanced composition. badgeVariants and badgeNumberVariants return the class string via CVA; statusConfig is the color+icon+label map keyed by status. |
When to use
- Transaction / entity status inside table rows (StatusBadge).
- Category or tag next to a title (Badge).
- Count on an IconButton (BadgeNumber — inbox, notifications, unread).
- Verified / trusted flag on an identity (Badge variant='verified').
When not to use
- Anything clickable — use Button or a chip with a close/action.
- Body text emphasis — use bold or a Callout.
- Blocking messages — use Dialog or AlertDialog.
- Persistent notices above a form — use Alert.
Usage
- Use the variant that matches meaning: success = approved, destructive = declined, verified = trusted identity.
- Keep the label to 1-2 words.
- For counts, cap at 99+ so the pill does not push its neighbor.
- For StatusBadge, override children only when the domain wording differs (Approved vs Succeeded).
- Don't stack more than 2 Badges next to a single title — it becomes noise.
- Don't make Badges clickable — use a Button variant instead.
- Don't invent a new status color — reach for one of the six StatusBadge tones or map yours to the closest one.
- Don't use variant='destructive' as decoration — reserve red for real declines or errors.
Related
Cross-links to atoms and patterns you may reach for next.