# Badge

> A small label for status, category, or metadata.

- Category: Data display
- Import: `import { Badge } from "@sagui/ui";`
- Page: /components/badge

```tsx
import { Badge } from "@sagui/ui";

export function Hero() {
  return <Badge tone="success">Live</Badge>;
}
```

## When to use

- Short statuses next to titles or in table cells, such as Live, Draft, or Failed.
- Counts or states that change in place and should morph instead of jump.
- Tagging a row with one tone plus an optional icon.

## When not to use

- Use alert or toast when the message needs a full sentence.
- Use chip-group when people toggle the values.
- Use stat-card for a headline number with a trend.

## Installation

Install the package and import the styles once. The [installation guide](/docs/installation) covers the Tailwind setup.

```bash
npm install @sagui/ui
```

## Usage

```tsx
import { Check } from "lucide-react";
import { Badge } from "@sagui/ui";

export function DeployStatus({ live }: { live: boolean }) {
  return (
    <Badge tone={live ? "success" : "neutral"} icon={live ? <Check size={12} /> : undefined}>
      {live ? "Live" : "Draft"}
    </Badge>
  );
}
```

## Examples

### Tones

```tsx
import { Badge } from "@sagui/ui";

export function Tones() {
  return (
    <div className="flex flex-wrap gap-2">
      <Badge tone="neutral">Draft</Badge>
      <Badge tone="success">Live</Badge>
      <Badge tone="info">Beta</Badge>
      <Badge tone="warning">Review</Badge>
      <Badge tone="danger">Failed</Badge>
    </div>
  );
}
```

### Sizes

```tsx
import { Badge } from "@sagui/ui";

export function Sizes() {
  return (
    <div className="flex items-center gap-2">
      <Badge size="sm" tone="info">Small</Badge>
      <Badge size="md" tone="info">Medium</Badge>
    </div>
  );
}
```

### With an icon

```tsx
import { Check, Clock } from "lucide-react";
import { Badge } from "@sagui/ui";

export function WithIcon() {
  return (
    <div className="flex flex-wrap gap-2">
      <Badge tone="success" icon={<Check size={12} />}>Deployed</Badge>
      <Badge tone="warning" icon={<Clock size={12} />}>Pending</Badge>
    </div>
  );
}
```

### Morphing status

Change the children, tone and icon on the same badge to get the morph.

```tsx
import { useState } from "react";
import { Check, Clock } from "lucide-react";
import { Badge, Button } from "@sagui/ui";

export function Morphing() {
  const [done, setDone] = useState(false);
  return (
    <div className="flex items-center gap-4">
      <Badge tone={done ? "success" : "warning"} icon={done ? <Check size={12} /> : <Clock size={12} />}>
        {done ? "Deployed" : "Deploying"}
      </Badge>
      <Button variant="outline" size="sm" onClick={() => setDone((value) => !value)}>Toggle</Button>
    </div>
  );
}
```

## API reference

### Badge

A small status pill whose label and icon morph in place when they change.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tone` | `"neutral" \| "success" \| "info" \| "warning" \| "danger"` | `"neutral"` | Color of the pill. |
| `size` | `"sm" \| "md"` | `"md"` | Height and text size. |
| `icon` | `ReactNode` | – | Leading icon. A different icon component crossfades in. |
| `children` | `ReactNode` | – | Label. String or number children get the rolling text swap; other nodes render as is. |
| `...props` | `HTMLAttributes<HTMLSpanElement>` | – | Forwarded to the root span, including className. |

## Accessibility

- Renders a plain span, so it is read inline with surrounding text.
- The icon is aria-hidden; the label must state the status on its own, not rely on tone color.
- Outgoing labels are hidden from assistive tech while they fade, so only the current text is read.
- It does not announce changes. Put it inside a live region if a status update must be spoken.

## Motion

- A new label rises in with a short blur while the old one lifts away, and the pill width springs to fit.
- Passive reflows such as font swaps resize instantly; only a content change springs.
- Reduced motion swaps the label with a quick fade and snaps the width.

## Responsive behavior

- The pill sizes to its label and never wraps, so keep labels to a word or two in narrow cells.
- Hover styles apply only on hover-capable fine pointers.

## Performance

- Each badge has a ResizeObserver for the width spring; fine per row, but avoid thousands in one table.
- Font swaps and passive reflows resize instantly; only content changes animate.

## Notes

- Use for short statuses and counts next to titles or in table cells. Use alert or toast for messages with sentences.
- Keep the badge mounted and change its children to get the morph; remounting with a new key loses it.

## Related

- [Alert](/components/alert): A persistent message that helps people recover or continue.
