Skip to content
shadcn.io is not affiliated with official shadcn/ui

Shadcn Badge for React and Tailwind

A compact label for status, counts, and categories.

Favorite

Installation

bunx --bun shadcn@latest add https://kit.dev/r/badge.json

Anatomy

A single element. Optional icons or a spinner are children, not separate parts.

Badge
└── content (text, icon, spinner, …)

Usage

import { Badge } from "@/components/ui/badge";
<Badge>Favorite</Badge>

Keep the label short. Badge is for status, counts, and categories — not body copy.

Variants

variant sets color. Status colors (success, info, warning, destructive) use the CSS variables from installation.

Default

Secondary

Outline

Success

Info

Warning

Destructive

Sizes

size controls height and horizontal padding. Icons inside the badge are size-3 unless you set a size-* class on the SVG.

SizeHeightPadding
smh-5 (1.25rem)px-1
mdh-5.5 (1.375rem)px-1.5
lgh-6.5 (1.625rem), text-smpx-2

Small

Medium

Large

Pill

pill uses rounded-full. With an icon child, end padding increases so the glyph is not clipped.

Examples

asChild merges badge styles onto a single child. Use it for Link or a.

With icon

Put the icon before the label. Decorative icons should be aria-hidden="true".

With spinner

Use Spinner for in-progress states. Hide it from assistive tech when the text already describes the status.

Count

Numeric counts work well with pill.

In a button

Place a badge after the button label. Negative end margin (-me-1) lines it up with the button padding.

Custom color

Override background, text, and border with className. Prefer semantic tokens when a variant already exists.

Guides

Badge vs Status vs Alert

BadgeStatusAlert
RoleShort label or countDot / indicatorMessage the user should notice
Typical contentText, optional iconEmpty or a tiny iconTitle, description, actions
Live regionNoNo (aria-hidden)role="alert" by default

Use Announcement for an inline banner; nest a Badge there for the category chip.

asChild

asChild comes from Ark’s factory. The child must be a single element (a, Link, button). Badge classes, data-slot, data-variant, data-size, and data-pill merge onto that element.

<Badge asChild variant="info">
  <Link href="/changelog">New</Link>
</Badge>

When the child is a or button, hover styles apply and a 44×44 CSS hit area is added on coarse pointers.

Using badgeVariants

badgeVariants is the same tv() recipe the component uses. Apply it when you need badge chrome on an element you already own:

import { badgeVariants } from "@/components/ui/badge";

<span className={badgeVariants({ size: "sm", variant: "outline" })}>
  Draft
</span>

API Reference

shadcn.io Badge is a styled span (Ark factory), not a Zag machine. There is no context hook or root provider.

asChild merges props onto a single child element.

Badge

Root. Renders a span.

PropTypeDefaultDescription
variant"default" | "secondary" | "outline" | "success" | "info" | "warning" | "destructive""default"Color treatment.
size"sm" | "md" | "lg""md"Height and padding. See Sizes.
pillbooleanfalseFully rounded ends. Extra end padding when an SVG child is present.
asChildbooleanfalseRender the child element instead of a span.
classNamestring-Class names on the root.

Native span attributes (id, title, aria-*, …) pass through. There is no role by default.

AttributeDescription
data-slotbadge
data-variant"default", "secondary", "outline", "success", "info", "warning", or "destructive"
data-size"sm", "md", or "lg"
data-pill"true" when pill is set

badgeVariants

Exported tv() recipe. Same variant, size, and pill keys as Badge.

badgeVariants({ variant: "secondary", size: "sm", pill: true });

BadgeVariant

Type alias for the variant union: "default" | "secondary" | "outline" | "success" | "info" | "warning" | "destructive".

BadgeProps

Props of Badge: span attributes plus VariantProps<typeof badgeVariants>.

Accessibility

Badge is not a live region and is not a composite widget. The visible text is the accessible name.

  • Decorative icons (the label already explains the meaning): aria-hidden="true".
  • If the icon is the only cue (icon-only badge), set aria-label on Badge.
  • Do not use Badge as a button unless asChild renders a real button (or a link for navigation).
  • Spinner defaults to role="status". Pass aria-hidden="true" when adjacent text already describes the state.

Keyboard support

A default badge is not focusable.

KeyDescription
TabMoves to a badge only when asChild renders a focusable element (a, button, …).
EnterActivates that focusable child.
SpaceActivates a button child.