Skip to content
Status & Labels primitive

Badge

Six semantic tones across three shapes, kept as separate axes — so a red outline badge is something you can ask for rather than something the variant list forgot.

91 linesNo dependenciesAdded 13 Sept 2026
  • badge
  • label
  • tag
  • pill
  • chip

What's included

  • components/ui/badge.tsx
  • No runtime dependencies

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add badge

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Live

Interactive, in your current theme. Every state below is the real component — type in it, tab through it, switch the theme.

ProPaidTrialOverdueBeta
SolidOutlineSoft pill

Source

One file. Create components/ui/badge.tsx and paste.

components/ui/badge.tsx
/**
 * <Badge> — a small piece of labelled text.
 *
 * Six tones and three shapes, and the only interesting decision is that
 * tone is separate from shape. Most badge components fuse the two —
 * `variant="destructive"` implying both a red colour and a solid fill — and
 * then a design needs a red outline badge and there is no way to ask for
 * one.
 *
 * The tones are semantic rather than colours: `success`, not `green`. A
 * badge named after its colour is renamed the first time the palette
 * changes, and every call site has to be found.
 */

import * as React from 'react'

export type BadgeTone = 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'info'
export type BadgeShape = 'solid' | 'soft' | 'outline'

const TONES: Record<BadgeTone, Record<BadgeShape, string>> = {
  neutral: {
    solid: 'bg-foreground text-background',
    soft: 'bg-muted text-muted-foreground',
    outline: 'border border-border text-muted-foreground',
  },
  brand: {
    solid: 'bg-primary text-primary-foreground',
    soft: 'bg-primary/10 text-primary',
    outline: 'border border-primary/40 text-primary',
  },
  success: {
    solid: 'bg-emerald-600 text-white',
    soft: 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400',
    outline: 'border border-emerald-500/40 text-emerald-700 dark:text-emerald-400',
  },
  warning: {
    solid: 'bg-amber-500 text-amber-950',
    soft: 'bg-amber-500/10 text-amber-700 dark:text-amber-400',
    outline: 'border border-amber-500/40 text-amber-700 dark:text-amber-400',
  },
  danger: {
    solid: 'bg-destructive text-destructive-foreground',
    soft: 'bg-destructive/10 text-destructive',
    outline: 'border border-destructive/40 text-destructive',
  },
  info: {
    solid: 'bg-sky-600 text-white',
    soft: 'bg-sky-500/10 text-sky-700 dark:text-sky-400',
    outline: 'border border-sky-500/40 text-sky-700 dark:text-sky-400',
  },
}

export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
  tone?: BadgeTone
  shape?: BadgeShape
  size?: 'sm' | 'md'
  icon?: React.ReactNode
  /** Pill rather than rounded rectangle. Use for counts and statuses. */
  pill?: boolean
}

export function Badge({
  tone = 'neutral',
  shape = 'soft',
  size = 'md',
  pill = false,
  icon,
  className = '',
  children,
  ...rest
}: BadgeProps) {
  return (
    <span
      className={[
        'inline-flex shrink-0 items-center gap-1 font-medium leading-none',
        'whitespace-nowrap',
        pill ? 'rounded-full' : 'rounded-md',
        size === 'sm' ? 'h-5 px-1.5 text-[11px]' : 'h-6 px-2 text-xs',
        TONES[tone][shape],
        className,
      ]
        .filter(Boolean)
        .join(' ')}
      {...rest}
    >
      {icon}
      {children}
    </span>
  )
}

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
toneBadgeTone'neutral'
shapeBadgeShape'soft'
size'sm' | 'md''md'
iconReact.ReactNode—
pillPill rather than rounded rectangle. Use for counts and statuses.booleanfalse

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

More status & labels

View all
Open the full page for this primitive

Status Badge

A dot and a word, with no icon-only mode by design — one in twelve men cannot separate the red state from the green one, so the word is structural rather than optional.

Status & Labels104 linesNo deps