hope-uiearly-preview

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

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

primaryneutralsuccessinfowarningdanger
solid
Badge
Badge
Badge
Badge
Badge
Badge
inverted
Badge
Badge
Badge
Badge
Badge
Badge
soft
Badge
Badge
Badge
Badge
Badge
Badge
subtle
Badge
Badge
Badge
Badge
Badge
Badge
outline
Badge
Badge
Badge
Badge
Badge
Badge
dot
Badge
Badge
Badge
Badge
Badge
Badge
The list below is non-exhaustive
<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.

api-gatewayLive
web-frontendDeploying
billing-workerFailed
legacy-cronPaused
<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.

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

3842
5
<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.

Render as an anchor
<Badge
  variant="soft"
  colorScheme="primary"
  render={(props) => <a href="#" {...props} />}
>
  Linkable tag
</Badge>

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

Parts

Every part carries a data-slot attribute you can target, and each is addressable by name through slotClasses.

Slotdata-slotDescription
rootbadgeThe rendered element (a span by default).
labelbadge-labelWraps the children. Rendered only when children is set.
startDecoratorbadge-start-decoratorLeading decorator wrapper. Rendered only when startDecorator is set.
endDecoratorbadge-end-decoratorTrailing decorator wrapper. Rendered only when endDecorator is set.
dotbadge-dotThe 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

theme.ts
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" },
    },
  },
});
App.tsx
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.

PropDefaultType
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.
fullWidthfalse
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 colorScheme is 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. The dot variant'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.