# Badge (/docs/badge)





`Badge` is a display `<span>` styled with the kit's (variant × color) system. The shape and palette axes mirror [`Button`](/docs/button), so a chip and an action share the same accent language. It composes onto a real control with [`asChild`](#accessibility) when a badge needs to be clickable.

```tsx
import { Badge } from '@42-central/ui-react/badge';

<Badge color="green">Validated</Badge>
<Badge variant="outline" color="red">Failed</Badge>
<Badge variant="gradient">Pro</Badge>
```

## Playground [#playground]

<story.Playground />

### Full prop browser [#full-prop-browser]

<story.WithControl />

## Variants [#variants]

Five shape treatments. Four of them — `filled`, `light`, `outline`, `subtle` — are the set [`Button`](/docs/button) uses; `gradient` is Badge's own. The default variant is `light` (badges are usually soft), not `filled`.

<Preview align="block" code="&#x22;<Badge variant=\&#x22;filled\&#x22;>Filled</Badge>\n<Badge variant=\&#x22;light\&#x22;>Light</Badge>\n<Badge variant=\&#x22;outline\&#x22;>Outline</Badge>\n<Badge variant=\&#x22;subtle\&#x22;>Subtle</Badge>\n<Badge variant=\&#x22;gradient\&#x22;>Gradient</Badge>&#x22;">
  <div className="flex flex-wrap items-center gap-2">
    <Badge variant="filled">
      Filled
    </Badge>

    <Badge variant="light">
      Light
    </Badge>

    <Badge variant="outline">
      Outline
    </Badge>

    <Badge variant="subtle">
      Subtle
    </Badge>

    <Badge variant="gradient">
      Gradient
    </Badge>
  </div>
</Preview>

`gradient` is palette-independent — it ignores `color` and paints its own linear gradient, which you steer with the `gradient` prop:

```tsx
<Badge variant="gradient" gradient={{ from: 'cyan', to: 'blue', dir: 'to-br' }}>Pro</Badge>
```

## Colors [#colors]

Any color in the palette pairs with any variant (except `gradient`). Use it for status semantics: `green` for success, `red` for failure, `orange` for in-progress.

<Preview align="block" code="&#x22;<Badge variant=\&#x22;light\&#x22;  color=\&#x22;green\&#x22;>Validated</Badge>\n<Badge variant=\&#x22;light\&#x22;  color=\&#x22;orange\&#x22;>Pending</Badge>\n<Badge variant=\&#x22;filled\&#x22; color=\&#x22;red\&#x22;>Failed</Badge>\n<Badge variant=\&#x22;outline\&#x22; color=\&#x22;neutral\&#x22;>Queued</Badge>&#x22;">
  <div className="flex flex-col gap-2">
    <div className="flex flex-wrap gap-2">
      {COLORS.map((c) => (
              <Badge key={c} variant="light" color={c}>
                {c}
              </Badge>
            ))}
    </div>

    <div className="flex flex-wrap gap-2">
      {COLORS.map((c) => (
              <Badge key={c} variant="filled" color={c}>
                {c}
              </Badge>
            ))}
    </div>
  </div>
</Preview>

## Sizes [#sizes]

<Preview align="block" code="&#x22;<Badge size=\&#x22;xs\&#x22;>Extra small</Badge>\n<Badge size=\&#x22;sm\&#x22;>Small</Badge>\n<Badge size=\&#x22;md\&#x22;>Medium</Badge>\n<Badge size=\&#x22;lg\&#x22;>Large</Badge>\n<Badge size=\&#x22;xl\&#x22;>Extra large</Badge>&#x22;">
  <div className="flex flex-wrap items-center gap-2">
    <Badge size="xs">
      Extra small
    </Badge>

    <Badge size="sm">
      Small
    </Badge>

    <Badge size="md">
      Medium
    </Badge>

    <Badge size="lg">
      Large
    </Badge>

    <Badge size="xl">
      Extra large
    </Badge>
  </div>
</Preview>

## API [#api]

<TypeTable
  type="{
  variant: {
    type: &#x22;'filled' | 'light' | 'outline' | 'subtle' | 'gradient'&#x22;,
    default: &#x22;'light'&#x22;,
    description: &#x22;Visual treatment.&#x22;,
  },
  color: {
    type: &#x22;'brand' | 'neutral' | 'red' | 'orange' | 'yellow' | 'green' | 'teal' | 'cyan' | 'blue' | 'purple' | 'pink' | (string & {})&#x22;,
    description:
      'Accent palette. Omitted by default — the slot vars cascade from :root. Ignored by variant=&#x22;gradient&#x22;. Custom values work as long as a matching [data-color=&#x22;…&#x22;] block exists in colors.css.',
  },
  gradient: {
    type: &#x22;{ from?: string; to?: string; via?: string; dir?: GradientDir }&#x22;,
    description:
      'Gradient stops and direction. Only applied when variant=&#x22;gradient&#x22;; unset fields keep the defaults (purple-300 → pink-400, to-r).',
  },
  size: {
    type: &#x22;'xs' | 'sm' | 'md' | 'lg' | 'xl'&#x22;,
    default: &#x22;'md'&#x22;,
    description: &#x22;Height + padding + text scale.&#x22;,
  },
  asChild: {
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
    description: &#x22;Render children as the root element (Ark UI polymorphism).&#x22;,
  },
  testId: {
    type: &#x22;string&#x22;,
    description:
      &#x22;Renders as `data-testid` on the rendered `<span>`. See [Testing](/docs/testing).&#x22;,
  },
}"
/>

All other props are forwarded to the underlying `<span>` (or `asChild` element).

## Accessibility [#accessibility]

Badge renders a `<span>`: nothing to focus, nothing to click. If you need an interactive badge (a tag filter, a count that reveals its detail), use `asChild` with a real `<button>` so the semantics line up — the kit's [focus ring](/docs/focus) comes with it, so you supply no `className` for it:

<Preview align="block" code="&#x22;<Badge asChild color=\&#x22;brand\&#x22;>\n  <button type=\&#x22;button\&#x22; onClick={...}>Remove tag</button>\n</Badge>&#x22;">
  <Badge color="brand">
    <button type="button">
      Remove tag
    </button>
  </Badge>
</Preview>

### As a hover-card trigger [#as-a-hover-card-trigger]

`asChild` on both sides collapses `HoverCard.Trigger` and `Badge` into a single element that *is* the badge — one DOM node, keyboard-reachable, with the ring hugging the badge's own radius. This is the idiomatic way to show a compact value that reveals its detail on hover or focus, e.g. a count standing in for a list too long to render inline:

```tsx
<HoverCard.Root>
  <HoverCard.Trigger asChild>
    <Badge asChild variant="light" aria-label="12 campuses">
      <button type="button">12</button>
    </Badge>
  </HoverCard.Trigger>
  <HoverCard.Content>{/* the full list */}</HoverCard.Content>
</HoverCard.Root>
```

Give the button an `aria-label` when the visible text is a bare number — "12" alone doesn't say what it counts.
