Badge
Small non-interactive tag. Three variants ship in the kit: Badge (solid label — default / secondary / outline / destructive / verified), StatusBadge (soft-tinted transaction/entity status with a filled 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)
inline-flex rounded-md px-2 py-0.5 text-xs font-semibold. 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 (color/15 bg + color/foreground text) 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').
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" />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.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.