# Badge

Label a status, category, or count.

Draft status badge

[Complete TSX file](/examples/badge/default.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return <Badge>Draft</Badge>;
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/badge
```

With npm: `npx shadcn@4.18.0 add @siteplane/badge`. [Registry JSON](/registry/badge.json) lists source files and dependencies.

## Usage

```tsx
import { Badge } from "@/siteplane/components/badge";

<Badge>Draft</Badge>;
```

## Variants

`default` is a white surface with a hairline contour. `outline` keeps only the contour, so whatever the badge sits on shows through: it matches `default` on white and suits chips inside fields and on tinted surfaces. `secondary` is a light, translucent neutral fill. `accent` (the brand color), `inverse` (black in Light) and `destructive` are solid fills for emphasis. `info`, `success`, `warning` and `error` tint the badge with a status color; `info` is the tint of the accent. Use a visible label as well as color to communicate status.

Badge variants

[Complete TSX file](/examples/badge/variants.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap gap-2">
      <Badge>Default</Badge>
      <Badge variant="secondary">Secondary</Badge>
      <Badge variant="outline">Outline</Badge>
      <Badge variant="accent">Accent</Badge>
      <Badge variant="inverse">Inverse</Badge>
      <Badge variant="destructive">Destructive</Badge>
      <Badge variant="info">Info</Badge>
      <Badge variant="success">Success</Badge>
      <Badge variant="warning">Warning</Badge>
      <Badge variant="error">Error</Badge>
    </div>
  );
}
```

## Secondary

`secondary` is the quiet neutral fill, mixed from the kit's tokens: the muted grey at 60 % in Light, and 6 % of the text color in Dark, which lightens the surface it sits on. It stays translucent, so it reads on a white card, on a tinted row and on a dark card, and suits labels that sit beside other content without competing with it, such as the sections of a site in a list.

Secondary badges on a white and a tinted row

[Complete TSX file](/examples/badge/secondary.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

const sites = [
  {
    current: true,
    name: "northlight.studio",
    sections: ["Pages", "Blog", "Shop"],
  },
  {
    current: false,
    name: "copperleaf.spa",
    sections: ["Pages", "Bookings"],
  },
] as const;

export default function Example() {
  return (
    <ul aria-label="Sites" className="grid w-full max-w-md gap-1 text-sm">
      {sites.map((site) => (
        <li
          aria-current={site.current ? "true" : undefined}
          className={`flex min-w-0 items-center justify-between gap-4 rounded-md px-3 py-2 ${
            site.current ? "bg-[var(--sp-rw-bg-accent-translucent)]" : ""
          }`}
          key={site.name}
        >
          <span className="min-w-0 truncate" title={site.name}>
            {site.name}
          </span>
          <span className="flex shrink-0 gap-1">
            {site.sections.map((section) => (
              <Badge key={section} variant="secondary">
                {section}
              </Badge>
            ))}
          </span>
        </li>
      ))}
    </ul>
  );
}
```

## Emphasis

`accent` and `inverse` are solid, so keep them small and few. Use `accent` to flag something new and `inverse` for a count. A count needs a contextual accessible name, here as hidden text.

A New label and an unread count

[Complete TSX file](/examples/badge/emphasis.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-x-8 gap-y-3 text-sm">
      <span className="inline-flex items-center gap-2">
        Release notes
        <Badge size="sm" variant="accent">
          New
        </Badge>
      </span>
      <span className="inline-flex items-center gap-2">
        Inbox
        <Badge size="sm" variant="inverse">
          12
          <span className="sr-only"> unread messages</span>
        </Badge>
      </span>
    </div>
  );
}
```

## Sizes

`sm`, `md`, `lg` and `xl` badges are 16, 20, 24 and 28 px high, and their type and icons grow with them. The omitted size inherits the provider control size. Without an override it is `lg`.

Badge sizes

[Complete TSX file](/examples/badge/sizes.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Badge size="sm">Small</Badge>
      <Badge size="md">Medium</Badge>
      <Badge size="lg">Large</Badge>
      <Badge size="xl">Extra large</Badge>
    </div>
  );
}
```

Badge text takes the weight `--sp-rw-badge-font-weight`, which is unset and falls back to 450. Set it after the base import to give every badge another weight, for example to match another [typeface](/docs/customization/theming/#typeface).

## Long labels

A badge is never wider than the space it sits in. Its text stays on one line and ends with an ellipsis where the badge meets the edge of that space, and the full text is the label's title, so a long value never pushes a card, a list row or a group of tags apart. In a table cell, a tab or a button, where text stays on one line, the badge keeps its full width like the text beside it. A count never truncates: the `count` profile grows as a pill, at least as wide as it is high.

## Shape

`shape="pill"` rounds a badge fully at every size, like the pill Button. Without it, a badge takes the theme's badge radius, `--sp-rw-radius-badge`, which is the small `--sp-rw-radius-xs` by default. To change every badge at once, set the token in your [theme](/docs/customization/theming/#shared-dimensions).

Default and pill badges

[Complete TSX file](/examples/badge/shape.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Badge>Default</Badge>
      <Badge shape="pill">Pill</Badge>
    </div>
  );
}
```

## With icon

Place a decorative icon before the label; the badge sizes it automatically.

Status badges with icons

[Complete TSX file](/examples/badge/composition.tsx)

```tsx
"use client";

import { CheckIcon, ClockIcon } from "lucide-react";
import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Badge variant="success">
        <CheckIcon aria-hidden="true" />
        Verified
      </Badge>
      <Badge variant="outline">
        <ClockIcon aria-hidden="true" />
        Scheduled
      </Badge>
    </div>
  );
}
```

## With status dot

Add a small dot before the label to make statuses easy to scan in lists. The label carries the meaning, so hide the dot from assistive technology.

Invoice status badges

[Complete TSX file](/examples/badge/status-dot.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <Badge variant="outline">
        <span aria-hidden="true" className="size-1.5 rounded-full bg-success" />
        Paid
      </Badge>
      <Badge variant="outline">
        <span aria-hidden="true" className="size-1.5 rounded-full bg-warning" />
        Pending
      </Badge>
      <Badge variant="outline">
        <span
          aria-hidden="true"
          className="size-1.5 rounded-full bg-destructive"
        />
        Failed
      </Badge>
    </div>
  );
}
```

## Status

`profile="status"` writes a state in words after a colored dot, without fill or contour, so it reads well in a list row or in running text. The variant gives the dot its tone: `success`, `warning`, `error` or `destructive`, and `info` or `accent`; every other variant shows the gray of an offline [Avatar](/docs/components/avatar/#status-indicator). The words carry the state in the secondary text color, at the badge's size and weight, and the dot repeats it, hidden from assistive technology. The dot grows with the size, 6 px at `sm` and `md` and 8 px at `lg` and `xl`, with as much space before the words.

Site statuses in a list and in a sentence

[Complete TSX file](/examples/badge/status.tsx)

```tsx
"use client";

import { Badge, type BadgeVariant } from "@/siteplane/components/badge";

const sites: { name: string; status: string; tone: BadgeVariant }[] = [
  { name: "northlight.studio", status: "Live", tone: "success" },
  { name: "shop.northlight.studio", status: "Building", tone: "info" },
  {
    name: "docs.northlight.studio",
    status: "Needs attention",
    tone: "warning",
  },
  { name: "staging.northlight.studio", status: "Failed", tone: "error" },
  { name: "archive.northlight.studio", status: "Paused", tone: "default" },
];

export default function Example() {
  return (
    <div className="grid w-full max-w-md gap-4 text-sm">
      <ul aria-label="Sites" className="grid gap-3">
        {sites.map((site) => (
          <li
            className="flex min-w-0 items-center justify-between gap-4"
            key={site.name}
          >
            <span className="min-w-0 truncate" title={site.name}>
              {site.name}
            </span>
            <Badge profile="status" variant={site.tone}>
              {site.status}
            </Badge>
          </li>
        ))}
      </ul>
      <p className="text-muted-foreground">
        The checkout is{" "}
        <Badge profile="status" variant="success">
          Live
        </Badge>{" "}
        again since the last deploy.
      </p>
    </div>
  );
}
```

## Link

Use `render` to style a link as a badge. It keeps link semantics and gains hover and focus styles; otherwise a badge is a non-interactive span.

Badge rendered as a link

[Complete TSX file](/examples/badge/link.tsx)

```tsx
"use client";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return <Badge render={<a href="#changelog" />}>Release notes</Badge>;
}
```

## Light

Give the one announcement on a page a light: two soft lights, the brand accent and an amber highlight, run around the badge's edge. Use it on an opaque badge such as the default one, at most once per page. Reduced motion keeps the lights still, and hover makes the glow a little stronger instead of tinting the badge.

Badge with light

[Complete TSX file](/examples/badge/light.tsx)

```tsx
"use client";

import { ArrowRightIcon } from "lucide-react";

import { Badge } from "@/siteplane/components/badge";

export default function Example() {
  return (
    <Badge light render={<a href="/docs/changelog/" />} shape="pill" size="xl">
      New: Charts
      <ArrowRightIcon aria-hidden="true" />
    </Badge>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | default \| secondary \| outline \| accent \| inverse \| destructive \| info \| success \| warning \| error | default | White with a hairline (default), neutral (the light, translucent secondary and outline), solid fill (accent, inverse, destructive) or tinted status color (info, success, warning, error). A count badge is inverse and an entitlement badge info unless set. brand still works as a deprecated alias of accent. |
| size | sm \| md \| lg \| xl | provider / lg | Height in fixed steps: sm 16, md 20, lg 24, xl 28 px. Type and icon follow the size. |
| shape | default \| pill | default | pill rounds the badge fully at every size. default takes the theme's badge radius, --sp-rw-radius-badge. |
| profile | count \| entitlement \| status \| verified | unset | count is a compact notification count, 16 px high with a full radius and 10 px type, solid unless a variant is set; it grows as a pill and never truncates. IconButton uses it. entitlement labels a plan or an entitlement, such as Business, with its icon in a 16 px box before the text; it takes the info tint unless a variant is set and draws no contour. status is a state in words after a colored dot, without fill or contour; the variant gives the dot its tone. verified is a solid accent circle with a ring in the surface color, placed on an avatar. It sizes the check icon you pass and needs an accessible name, such as visually hidden text. |
| light | boolean | false | Runs two soft lights around an opaque badge for the one announcement on a page. Reduced motion keeps them still. |
| render | ReactElement \| render function | span | Compose onto a link or another element. |
| className | string | unset | Additional classes. |
| children | ReactNode | unset | Visible label or count. Text children become one label, which ends with an ellipsis where the badge meets the edge of its space and carries its full text as its title; icons and other elements stay beside it. |

`BadgeProps`, `BadgeProfile` and `BadgeVariant` describe these contracts; `BadgeDeprecatedVariant` holds the earlier `brand`. `badgeVariants({ size, variant, className })` returns the utility classes Badge adds beside its stylesheet, with `lg` size and `default` variant; the look comes from the kit stylesheet through the attributes Badge renders, and the helper does not resolve provider size. Remaining attributes and refs follow the rendered element. [Composition contract](https://base-ui.com/react/handbook/composition).

[Read the installed source](/source/badge).

## Usage notes

A status update is not automatically announced. Put changing application feedback in an appropriate live region. A count needs a contextual accessible name; decorative icons should be hidden from assistive technology.
