Badge
Compact labels for status, categories, and counts with multiple visual styles.
Playground
<Badge>New</Badge>01 Examples
Border (visual separation on colored surfaces)
<div
class="bg-primary relative flex h-16 w-16 items-center justify-center rounded-full text-white"
>
JD
<Badge intent="success" variant="dot" placement="bottom-end" />
</div>
<div
class="bg-primary relative flex h-16 w-16 items-center justify-center rounded-full text-white"
>
JD
<Badge intent="success" variant="dot" placement="bottom-end" border />
</div>
<div
class="bg-warning text-text-on-warning relative flex h-16 w-16 items-center justify-center rounded-full"
>
AB
<Badge intent="danger" counter placement="top-end" border size="sm">5</Badge>
</div>Notification Counter
<div class="relative inline-block">
<Button variant="outlined" intent="neutral">Inbox</Button>
<Badge intent="danger" counter placement="top-end" border size="sm">3</Badge>
</div>
<div class="relative inline-block">
<Button variant="outlined" intent="neutral">Updates</Button>
<Badge intent="primary" counter placement="top-end" border size="sm">12</Badge>
</div>
<div class="relative inline-block">
<Button variant="outlined" intent="neutral">Alerts</Button>
<Badge intent="success" variant="dot" placement="top-end" border />
</div>02 Patterns
Badge serves five distinct use cases that look alike but behave differently. Picking the right
pattern up-front avoids API-shape regret later — particularly around removable vs interactive,
counter sizing, and placement-anchoring. The purpose axis makes that taxonomy first-class: status / tag / counter / dot / chip orchestrate the low-level visual props, so you
rarely set them by hand. One of them also picks a colour: purpose="tag" defaults to intent="neutral", because a category (a stage, a type, a
version) carries no severity — painting one as a status is the most common colour defect there
is. Pass intent explicitly to override.
1. Status Tag — stateful label
variant=soft for ambient lists, variant=outlined for higher-contrast tables. Never removable, never a number.<Badge variant="soft" intent="success">Active</Badge>
<Badge variant="soft" intent="warning">Pending</Badge>
<Badge variant="soft" intent="danger">Suspended</Badge>
<Badge variant="outlined" intent="neutral">Draft</Badge>
<Badge variant="outlined" intent="primary">Published</Badge>2. Counter — numeric pill
counter locks min-width to height, so a single digit renders as a circle rather than a squat pill — the first badge below is the same intent without it, for comparison. It holds 1–3 digits cleanly. Always pair with a placement when anchoring to a Button or Avatar — see the Notification Counter example above.<Badge intent="primary" size="sm">3 unread</Badge>
<Badge intent="primary" counter size="sm">3</Badge>
<Badge intent="danger" counter size="sm">12</Badge>
<Badge intent="success" counter size="sm">99+</Badge>
<Badge intent="warning" counter>247</Badge>3. Indicator Dot — presence/state marker
variant=dot strips all content and shrinks the badge to a presence indicator — typical on avatars (online/away/busy) or buttons (unread bell). Pair with border when overlaying a colored surface to cut a clean halo.<div
class="bg-primary-subtle text-primary-emphasis relative flex h-10 w-10 items-center justify-center rounded-full text-sm font-bold"
>
JD
<Badge intent="success" variant="dot" placement="bottom-end" border />
</div>
<div
class="bg-warning-subtle text-warning-emphasis relative flex h-10 w-10 items-center justify-center rounded-full text-sm font-bold"
>
AB
<Badge intent="warning" variant="dot" placement="bottom-end" border />
</div>
<div
class="bg-danger-subtle text-danger-emphasis relative flex h-10 w-10 items-center justify-center rounded-full text-sm font-bold"
>
MK
<Badge intent="danger" variant="dot" placement="bottom-end" border />
</div>4. Filter Chip — removable, user-applied
removable to show user-applied filters or selected tags that can be dismissed. Pair with onRemove. Press Delete / Backspace while focused to remove via keyboard.{#each filters as filter (filter)}
<Badge
variant="soft"
intent="primary"
removable
onRemove={() => removeFilter(filter)}
size="sm"
>
{filter}
</Badge>
{/each}
{#if filters.length === 0}
<span class="text-text-tertiary text-sm">No filters · </span>
<button
class="text-primary text-sm underline"
onclick={() => (filters = ['Active', 'Premium', 'Last 7 days'])}
>
reset
</button>
{/if}5. Inline Label — categorization in prose
variant=outlined or variant=soft over filled so the badge reads as taxonomy, not as a state change.The Beta release adds support for OKLCH color tokens and a redesigned Tier API . Existing consumers can opt-in per component.
<p class="text-text-secondary text-sm leading-loose">
The <Badge variant="outlined" intent="neutral" size="sm">Beta</Badge> release adds support for
<Badge variant="soft" intent="primary" size="sm">OKLCH</Badge>
color tokens and a redesigned
<Badge variant="soft" intent="success" size="sm">Tier API</Badge>. Existing consumers can
opt-in per component.
</p>When to pick which
| Pattern | Key Prop | Recommended Variant |
|---|---|---|
| Status Tag | variant | soft or outlined |
| Counter | counter · placement | filled |
| Indicator Dot | variant="dot" · border | dot (locked) |
| Filter Chip | removable · onRemove | soft |
| Inline Label | — | outlined or soft |
Mixing patterns (e.g. counter + removable, or filter-chip with `intent=danger`) usually
points at a different component — a deletable counter is a Chip, a danger-tinted filter is a state-tag of the search itself.
03 Customization
Gradient Tags
<Badge
slotClasses={{
base: 'bg-linear-to-r from-violet-500 to-fuchsia-500 text-white border-none shadow-md shadow-violet-500/20'
}}
>
Featured
</Badge>
<Badge
slotClasses={{
base: 'bg-linear-to-r from-cyan-500 to-blue-500 text-white border-none shadow-md shadow-cyan-500/20'
}}
>
New Release
</Badge>
<Badge
slotClasses={{
base: 'bg-linear-to-r from-amber-500 to-orange-500 text-white border-none shadow-md shadow-amber-500/20'
}}
>
Trending
</Badge>Glassmorphism
<Badge
unstyled
class="rounded-full border border-white/20 bg-white/15 px-3 py-1 text-xs font-medium text-white backdrop-blur-md"
>
PRO
</Badge>
<Badge
unstyled
class="rounded-full border border-white/20 bg-white/15 px-3 py-1 text-xs font-medium text-white backdrop-blur-md"
>
Beta
</Badge>
<Badge
unstyled
class="rounded-full border border-white/20 bg-white/15 px-3 py-1 text-xs font-medium text-white backdrop-blur-md"
pulse
>
Live
</Badge>Neon Glow
<Badge
unstyled
class="rounded-full border border-emerald-400/60 bg-emerald-400/10 px-3 py-1 text-xs font-semibold text-emerald-400 shadow-[0_0_12px_rgba(52,211,153,0.3)]"
>
Online
</Badge>
<Badge
unstyled
class="rounded-full border border-sky-400/60 bg-sky-400/10 px-3 py-1 text-xs font-semibold text-sky-400 shadow-[0_0_12px_rgba(56,189,248,0.3)]"
>
Syncing
</Badge>
<Badge
unstyled
class="rounded-full border border-rose-400/60 bg-rose-400/10 px-3 py-1 text-xs font-semibold text-rose-400 shadow-[0_0_12px_rgba(251,113,133,0.3)]"
>
Offline
</Badge>Fully Custom (unstyled)
<Badge
unstyled
class="text-text-primary inline-flex items-center gap-1.5 rounded-none border-2 border-current px-3 py-1 font-mono text-xs font-bold tracking-widest uppercase"
>
v2.4.0
</Badge>
<Badge
unstyled
class="inline-flex items-center gap-1.5 rounded-2xl bg-linear-to-br from-amber-200 to-orange-300 px-4 py-1.5 text-xs font-bold text-neutral-900 shadow-lg"
>
<StarIcon size={14} /> Editor's Pick
</Badge>A tag style that repeats across the app — like the gradient categories above — is one BlocksProvider preset (presets.Badge) applied via preset, not a copied slotClasses block. See Customization.
04 Accessibility
Built-in ARIA
Badges render with role="status" by default,
announcing content changes to screen readers. Removable badges include an accessible label
for the remove button. Set role="alert" for time-sensitive
notifications.
Keyboard
Interactive badges are focusable via Tab and activate with Enter / Space. Removable badges also respond to Delete / Backspace.
Reduced Motion
Pulse animations and Mint effects are suppressed when prefers-reduced-motion is enabled.
05 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
purpose required | dot | — | The canonical dot spelling — forces the pure-indicator look regardless of
variant. For the label roles use status / tag / counter / chip
(their own arm). | |
border | boolean | — | Add a ring outline in the page background color — useful to visually separate overlapping or positioned badges from their parent. | |
children | Snippet | — | Badge content (text, icons, numbers). | |
class | string | — | Extra classes merged onto the root element. | |
counter | boolean | — | Display as a compact pill for numeric counts (tightens padding, tabular-nums). | |
disabled | boolean | — | Visually disable the badge (reduced opacity, no pointer events). | |
intent variant | dangerneutralprimarysecondary +2 more | primary | Controls the color theme and semantic meaning of the Badge. Affects the overall appearance and user perception. Available options: danger, neutral, primary, and 3 more. | |
interactive | boolean | — | Enable hover/focus styles and keyboard activation. Automatically enabled when onclick is provided. | |
mint | MintProp | 'none' | Micro-interaction preset applied to the badge. Only applies while
interactive (purpose="chip", interactive, or onclick) and not
disabled. | |
onclick | (event: MouseEvent) => void | — | Click handler. Automatically enables interactive styles (and role="button"). | |
onHover | (hovered: boolean) => void | — | Called when the hover state changes. | |
onRemove | () => void | — | Fired when the remove button is clicked (only when removable is true). | |
placement | BadgePlacement | — | Anchor the badge absolutely within a position: relative parent. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ Badge: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
pulse | boolean | — | Add a pulsing animation to draw attention (e.g. for live indicators). | |
removable | boolean | — | Show a remove (×) button. | |
role | statusalertbadgebutton | — | ARIA role. A static badge is announced as "status"; an interactive badge
(onclick or purpose="chip", when not disabled) defaults to "button"
so assistive tech announces its activation semantics. Set explicitly to
override — e.g. "alert" for time-sensitive notifications. An explicit
value always wins over the derived default. | |
size variant | lgmdsmxs | md | Controls the dimensions, padding, and text size of the Badge. Affects the component's physical footprint. Available options: lg, md, sm, xs. | |
slotClasses | Partial<Record<BadgeSlots, string>> | — | Per-slot class overrides merged with tv styles. | |
tier variant | commitmodify | commit | Selects the semantic radius tier of the Badge — the shape family it belongs to (--radius-commit/-modify/-contain/-bridge). Shape is retuned per family in your theme, so this picks the family rather than a pixel value. Available options: commit, modify. | |
unstyled | boolean | — | Remove all default tv classes. | |
variant | filledoutlinedsoft | 'filled' | Visual variant. dot renders a pure indicator (content hidden); the label variants accept the full surface. |
06 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
BadgeProps | type | helper | 0 | — | |
BadgeVariants | type | variant | 0 | — | |
BadgeSlots | type | variant | 0 | Slot names derived from the tv() config — single source of truth for slotClasses. | |
BadgePlacement | type | helper | 1 | — | |
MintProp | type | helper | 1 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
BuiltinMintName | type | helper | 0 | Built-in mint names as a literal union, so the mint prop autocompletes
across every component — the single list the hand-curated playground knobs
and docs used to drift away from. |
07 Installation
Import
import { Badge } from '@urbicon-ui/blocks';