Components

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.

Updated Oct 2, 2026 by Juan Pablo Turina

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.

Approved
Playground
<Badge variant="default">"Approved"</Badge>

Anatomy

DefaultSucceeded8
  1. 1
    Badge (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).

  2. 2
    StatusBadge (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.

  3. 3
    BadgeNumber (count pill)

    h-5 min-w-5 rounded-full for counts. Same variants as Badge. Use for notification bells, inbox counters, unread markers.

Variants

DefaultSecondaryOutlineDestructiveVerified
<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

ApprovedNext
<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'.

CreatedPendingSucceededDeclinedRefundedInactiveArchivedPausedFailedWarning

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.

DraftPublishedScheduledUnread
{/* 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.

SucceededPendingDeclinedRefundedScheduled
SucceededPendingDeclinedRefundedScheduled
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.

Not publishedNetwork tokens offRequires manual revi…Insufficient funds o…
{/* 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

812399+
<BadgeNumber>8</BadgeNumber>
<BadgeNumber variant="secondary">12</BadgeNumber>
<BadgeNumber variant="outline">3</BadgeNumber>
<BadgeNumber variant="destructive">99+</BadgeNumber>
Three atoms, one file
Badge, StatusBadge and BadgeNumber live in the same file (src/components/ui/badge.tsx) because they share the same visual language and are almost always chosen against each other. Pick Badge for labels/tags, StatusBadge for canonical states with icons, BadgeNumber for counts.

Recipes

Ready-to-copy compositions covering the most common Yuno usages of this atom.

Transaction status in a table row

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.

TransactionAmountStatus
tx_a1b2$ 145.20Approved
tx_c3d4$ 89.00Pending
tx_e5f6$ 320.50Declined
<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>
Notification count on an IconButton

BadgeNumber pinned top-right of a bell/inbox IconButton via relative + absolute positioning. Cap at 99+ so it does not push the icon.

8
<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>
Category tag next to a title

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 / trusted flag

verified variant with a leading SealCheck (fill) to mark an account, merchant or method as verified. Reserved for trust signals — never for status.

Acme Merchants Ltd.Verified
<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.

Three atoms, one file. Pick Badge for labels, StatusBadge for canonical states with icons, BadgeNumber for counts.
import { Badge, BadgeNumber, StatusBadge } from "@/components/ui/badge";

Props

Everything else from the underlying HTML or Radix primitive is forwarded via ...props.

PropTypeDefaultDescription
Badge.variant"default" | "secondary" | "outline" | "destructive" | "verified""default"Solid label variants. Default = primary; verified = blue-500 (trust flag).
Badge.classNamestring—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.childrenReact.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.childrenReact.ReactNode—The count. Cap display at 99+ so the pill does not push its neighbor.
badgeVariants / badgeNumberVariants / statusConfighelpers—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

Do
  • 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
  • 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.

  • ButtonFor clickable chips or pills — Badge is non-interactive by design.
  • AlertFor a full-width banner instead of an inline tag.
  • CalloutFor a tinted docs box that emphasizes body text.
  • TooltipWrap a Badge to explain what the status means on hover.