Badge
An inline label for statuses, categories, and counts — with variants, color schemes, sizes, shapes, decorators, and a status dot.Import
import { Badge } from "@hope-ui/components/badge";Usage
Badge is a static, non-interactive label — a styled <span>. Drop text in and it inherits the
default soft / primary look.
<Badge>Badge</Badge>Variants
The variant prop sets the visual style. solid and inverted are filled; soft and subtle
are tonal; outline is a hollow chip; dot is neutral chrome with a role-colored status pip.
<Badge variant="solid">solid</Badge>
<Badge variant="soft">soft</Badge>
<Badge variant="subtle">subtle</Badge>
<Badge variant="outline">outline</Badge>
<Badge variant="dot">dot</Badge>On solid surfaces
inverted is the swap of solid — a light fill with role-colored text — so it is designed to sit on
a solid, colored surface (a toolbar, a banner) rather than the page background. It paints its own
dedicated {role}-inverted tokens (which default to the swapped values), so it stays legible by
design. warning renders as a dark chip, the honest result of a symmetric swap.
primary
neutral
success
danger
<div class="bg-surface-inverse p-6">
<Badge variant="inverted" colorScheme="primary">
primary
</Badge>
<Badge variant="inverted" colorScheme="success">
success
</Badge>
</div>Color schemes
Pair a variant with a colorScheme — one of the six semantic roles. Every variant is
role-aware: even dot keeps neutral chrome while coloring only its status pip.
<Badge variant="solid" colorScheme="primary">Primary</Badge>
<Badge variant="soft" colorScheme="success">Success</Badge>
<Badge variant="subtle" colorScheme="info">Info</Badge>
<Badge variant="outline" colorScheme="warning">Warning</Badge>
<Badge variant="dot" colorScheme="danger">Danger</Badge>Sizes
Four sizes, from xs to lg (heights step from 16px to 28px). The recipe scales padding, text,
radius, the status dot, and any decorator icon to match.
Extra small
Small
Medium
Large
<Badge variant="solid" size="xs">Extra small</Badge>
<Badge variant="solid" size="sm">Small</Badge>
<Badge variant="solid" size="md">Medium</Badge>
<Badge variant="solid" size="lg">Large</Badge>Shapes
shape controls the corner radius: sharp, rounded (default), and pill. circle squares the
aspect and drops the horizontal padding — the shape for a single glyph or count.
sharp
rounded
pill
3
<Badge variant="soft" shape="sharp">sharp</Badge>
<Badge variant="soft" shape="rounded">rounded</Badge>
<Badge variant="soft" shape="pill">pill</Badge>
<Badge variant="solid" colorScheme="danger" shape="circle" size="md">3</Badge>Status
The dot variant renders role-neutral chrome with a single role-colored pip — the natural fit for
a status indicator, where the label carries the meaning and the color reinforces it. It works
across every role.
Live
Deploying
Failed
Paused
<Badge variant="dot" colorScheme="success">Live</Badge>
<Badge variant="dot" colorScheme="warning">Deploying</Badge>
<Badge variant="dot" colorScheme="danger">Failed</Badge>
<Badge variant="dot" colorScheme="neutral">Paused</Badge>In context — a status column reads cleanly because each row states its state in words, with the pip as a scannable second channel.
<Badge variant="dot" colorScheme="success">Live</Badge>
<Badge variant="dot" colorScheme="warning">Deploying</Badge>
<Badge variant="dot" colorScheme="danger">Failed</Badge>Icons & decorators
Put content before or after the label with startDecorator and endDecorator. Both accept any
JSX — an inline <svg>, or an icon from your icon set. The recipe sizes decorator SVGs to the
badge's size and tightens the padding on the decorated side so the icon isn't over-spaced.
<Badge variant="soft" colorScheme="success" startDecorator={<CheckIcon />}>
Verified
</Badge>
<Badge variant="outline" colorScheme="danger" endDecorator={<CloseIcon />}>
Remove
</Badge>Counts & notifications
The circle shape turns a badge into a round count pip. Anchor one to the corner of an icon with
class (a ring in the surface color lifts it off the element beneath), and give it an
aria-label so the count is announced.
<Badge variant="solid" colorScheme="danger" shape="circle" size="sm">3</Badge>
<div class="relative inline-flex">
<BellIcon class="size-7" />
<Badge
variant="solid"
colorScheme="danger"
shape="circle"
size="xs"
class="absolute -top-1.5 -end-2 ring-2 ring-surface-raised"
aria-label="5 unread notifications"
>
5
</Badge>
</div>Full width
fullWidth stretches the badge to fill its container — useful as a block-level status ribbon.
Full width
<Badge variant="soft" colorScheme="info" fullWidth>
Full width
</Badge>Polymorphism
Render Badge as a different element or component with the render prop — a function that receives
Badge's fully-computed props and spreads them onto your element. This is how you make a linkable
tag without losing Badge's styling. There is no as prop; render is the single
polymorphism API.
<Badge
variant="soft"
colorScheme="primary"
render={(props) => <a href="#" {...props} />}
>
Linkable tag
</Badge>class and merges refs. Spreading Badge's span props onto a different element may need an as unknown as cast to satisfy TypeScript; the runtime behavior is unaffected.Theming
Badge's look comes from the active preset's recipe. You can override it at three levels, applied
in order (later wins a Tailwind conflict): recipe base → preset slotClasses → instance
slotClasses → class.
Parts
Every part carries a data-slot attribute you can target, and each is addressable by name through
slotClasses.
| Slot | data-slot | Description |
|---|---|---|
root | badge | The rendered element (a span by default). |
label | badge-label | Wraps the children. Rendered only when children is set. |
startDecorator | badge-start-decorator | Leading decorator wrapper. Rendered only when startDecorator is set. |
endDecorator | badge-end-decorator | Trailing decorator wrapper. Rendered only when endDecorator is set. |
dot | badge-dot | The status pip. Rendered only for the dot variant; marked aria-hidden. |
Overriding one badge
class merges onto the root slot (your utilities win conflicts). slotClasses targets any slot
by name. Use literal class strings so your Tailwind build can see them.
New
Ribbon
<Badge
variant="solid"
colorScheme="primary"
slotClasses={{ root: "px-4 tracking-wide uppercase" }}
>
New
</Badge>
<Badge variant="soft" colorScheme="success" class="rounded-none border-s-4 border-success">
Ribbon
</Badge>App-wide defaults & overrides
Set defaults and global part classes for every Badge through the theme with definePreset,
then pass the derived preset to ThemeProvider. defaultProps are resolved at instance ??
preset ?? builtin precedence, so an explicit prop on a single badge always wins.
Branded
Branded
import { definePreset } from "@hope-ui/theming";
import { hope } from "@hope-ui/presets/hope";
export const myPreset = definePreset(hope, {
components: {
badge: {
// Every recipe variant is defaultable.
defaultProps: { shape: "pill" },
// Global part classes, folded in before per-instance slotClasses.
slotClasses: { root: "uppercase tracking-wide" },
},
},
});import { ThemeProvider } from "@hope-ui/theming";
import { myPreset } from "./theme";
<ThemeProvider preset={myPreset}>
{/* every Badge below is pill-shaped & uppercase */}
</ThemeProvider>;Because Badge is a static label, every styling axis is defaultable through defaultProps:
variant, colorScheme, size, shape, and fullWidth. There is no transient state (no
loading/disabled) to exclude.
API
Props
Badge accepts every native <span> attribute in addition to the props below.
| Prop | Default | Type |
|---|---|---|
variant | "soft" | BadgeVariantVisual style: solid, inverted, soft, subtle, outline, dot. inverted is the on-color swap of solid, for solid surfaces. |
colorScheme | "primary" | BadgeColorSchemeSemantic role color: primary, neutral, success, info, warning, danger. Every variant is role-aware. |
size | "sm" | BadgeSizeDensity scale: xs, sm, md, lg (heights 16–28px). |
shape | "rounded" | BadgeShapeCorner shape: sharp, rounded, pill, circle. circle squares the aspect for a single glyph or count. |
fullWidth | false | booleanStretches the badge to fill its container's width. |
startDecorator | — | JSX.ElementLeading content before the label — typically an icon. Sized to the badge by the recipe. |
endDecorator | — | JSX.ElementTrailing content after the label — typically an icon. |
render | — | (props) => JSX.ElementRender Badge as another element or component. Receives Badge's computed props. The only polymorphism mechanism — there is no as prop. |
class | — | stringExtra classes merged onto the root slot; your utilities win conflicts via tailwind-merge. |
slotClasses | — | SlotClasses<"badge">Per-slot class overrides, keyed by root, label, startDecorator, endDecorator, dot. |
children | — | JSX.ElementThe badge label. |
Accessibility
Badge renders a plain <span> with no ARIA role, so it is read as inline text in the document
flow — the right default for a label. That puts the burden on content: the badge's meaning must
live in its text, not its color.
- Never rely on color alone. Every
colorSchemeis a distinct token, but color is a second channel, not the message (WCAG 1.4.1). Pair the warning color with the word "Deploying", not a bare colored dot. Thedotvariant's pip is decorative (aria-hidden) precisely so the label carries the state. - Give count and icon-only badges an accessible name. A count sitting on the corner of an icon
has no surrounding text, so add an
aria-label(e.g.aria-label="5 unread notifications") that states what the number means. - Announce live counts. If a badge's number updates in place, put
aria-live="polite"on it (or a wrapping region) so assistive tech announces the change without stealing focus. - Rendered as a link, via
render, the badge inherits the anchor's semantics and becomes focusable and keyboard-operable for free — no extra work needed, since Badge adds no behavior of its own.