docs / core / badge

Badge

the status of the thing it sits on. six semantic tones as soft pills, a mono variant for versions and keys, and a pulsing live state. it labels, it never clicks; a clickable pill is Chip.

intensity · recedesrsc · server

provenance

shaped from sisu-plus

layer

core / primitives

intensity

recedes · safe anywhere, including dense surfaces

composition

sits in Card headers, table rows, list items, next to a titlemono for machine values: versions, ids, env namesno onClick, no dismiss. interaction makes it a Chipnot a counter on an icon. that is NotificationBadge

a11y

plain text in a span · the live dot is aria-hiddenjest-axe

dependencies

class-variance-authority
live demo · try it out
Passing
customize
tonesemantic color role
labelthe text inside
monouppercase tag for versions, keys
livepulsing green, overrides tone
usagecopy
import { Badge } from "usva/primitives/badge";

<Badge tone="success">Passing</Badge>

props

proptypedefaultnotes
tone"neutral" | "accent" | "accent-alt" | "success" | "warning" | "danger""neutral"semantic color role, rendered as a soft pill.
monobooleanfalsean uppercase monospace tag, for versions, keys and metadata.
livebooleanthe live green with a pulsing dot. overrides tone.

get it

npx shadcn add https://usva.build/r/badge.jsoncopy
source · components/ui/badge.tsxexactly what this command copiescopy
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";

export const badgeVariants = cva(
  "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium tabular-nums",
  {
    variants: {
      tone: {
        neutral: "border-border bg-surface-2 text-muted",
        accent: "border-accent/25 bg-accent/12 text-accent",
        "accent-alt": "border-accent-alt/25 bg-accent-alt/12 text-accent-alt",
        success: "border-success/25 bg-success/12 text-success",
        warning: "border-warning/25 bg-warning/12 text-warning",
        danger: "border-danger/25 bg-danger/12 text-danger",
      },
      mono: {
        true: "font-mono text-[0.6875rem] font-semibold uppercase leading-none tracking-wide",
        false: "",
      },
    },
    defaultVariants: { tone: "neutral", mono: false },
  },
);

export interface BadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof badgeVariants> {
  live?: boolean;
}

export function Badge({
  className,
  tone,
  mono,
  live,
  children,
  ...props
}: BadgeProps) {
  return (
    <span
      className={cn(
        badgeVariants({ tone, mono }),
        live && "border-live/25 bg-live/12 text-live",
        className,
      )}
      {...props}
    >
      {live ? (
        <span
          aria-hidden="true"
          className="-ml-0.5 h-1.5 w-1.5 rounded-full bg-live animate-live-pulse"
        />
      ) : null}
      {children}
    </span>
  );
}