# Overlay badge

Pins a count or a dot to the corner of an icon, a button or an avatar, and says it in words.

- **Import:** `import { OverlayBadge } from "@booleanpress/ui/overlay-badge"`
- **Page:** <https://ui.booleanpress.com/components/overlay-badge> · @booleanpress/ui 0.2.0

## Usage

Wrap one element. The badge sits on its top-end corner (top-left in a right-to-left page), with a 2 px ring of the card colour around it, so it stands clear of what it covers.

```tsx
import { BellIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"

export function Alerts() {
  return (
    <OverlayBadge count={3} severity="danger" label="3 failed deliveries">
      <Button variant="outline" size="icon" aria-label="Delivery alerts">
        <BellIcon />
      </Button>
    </OverlayBadge>
  )
}
```

`count`, `max`, `dot`, `severity` and `size` go to the `Badge`; a count without a severity takes the primary fill. `label` is required: the badge in words, such as "3 failed deliveries". The drawn badge is hidden from assistive technology and the label is read instead, so write the label to stand on its own. When the count changes, change the label with it.

## Examples

### Icon

A count or a dot on a 24 px icon.

```tsx
import { BellIcon, CalendarIcon, MailIcon } from "lucide-react"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"

export default function OverlayBadgeIcon() {
  return (
    <div className="flex items-center justify-center gap-8">
      <OverlayBadge count={2} label="2 new notifications">
        <BellIcon className="size-6 text-foreground" aria-hidden />
      </OverlayBadge>
      <OverlayBadge count={4} severity="danger" label="4 bookings to confirm">
        <CalendarIcon className="size-6 text-foreground" aria-hidden />
      </OverlayBadge>
      <OverlayBadge dot label="New messages">
        <MailIcon className="size-6 text-foreground" aria-hidden />
      </OverlayBadge>
    </div>
  )
}
```

### Avatar

A count, or a status dot, on an avatar.

```tsx
import { Avatar, AvatarFallback } from "@booleanpress/ui/avatar"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"

export default function OverlayBadgeAvatar() {
  return (
    <div className="flex items-center justify-center gap-8">
      <OverlayBadge count={4} severity="danger" label="4 unassigned tickets">
        <Avatar size="lg">
          <AvatarFallback>AL</AvatarFallback>
        </Avatar>
      </OverlayBadge>
      <OverlayBadge dot severity="success" label="Online">
        <Avatar size="lg">
          <AvatarFallback>GH</AvatarFallback>
        </Avatar>
      </OverlayBadge>
    </div>
  )
}
```

### Dot

`dot` puts an 8 px dot on a button; the button is described by the label.

```tsx
import { BellIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"

export default function OverlayBadgeDot() {
  return (
    <div className="flex items-center justify-center gap-8">
      <OverlayBadge dot severity="info" label="New delivery alerts">
        <Button variant="outline" size="icon" aria-label="Delivery alerts">
          <BellIcon />
        </Button>
      </OverlayBadge>
      <OverlayBadge dot severity="danger" label="A sending domain failed verification">
        <Button variant="ghost">Domains</Button>
      </OverlayBadge>
    </div>
  )
}
```

### Max

`max` caps the count: `99+`, `999+`.

```tsx
import { InboxIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"

export default function OverlayBadgeMax() {
  return (
    <div className="flex items-center justify-center gap-10">
      <OverlayBadge count={128} max={99} severity="danger" label="128 failed emails">
        <InboxIcon className="size-6 text-foreground" aria-hidden />
      </OverlayBadge>
      <OverlayBadge count={1250} max={999} label="1,250 open tickets">
        <Button variant="outline">Tickets</Button>
      </OverlayBadge>
    </div>
  )
}
```

## Accessibility

**Semantics.** A `span` around the element, which keeps its own role. The badge is `aria-hidden`; the label is a visually hidden text with an id, and a focusable element gets `aria-describedby` pointing to it.

**Labels.** A focusable element (a button, a link) is described by the label: a screen reader says its name, then the label ("Delivery alerts, button, 3 failed deliveries"), and the label is hidden so it is not read twice. Any other element (an icon, an avatar) has the label read after it in the page.

**Focus.** The wrapper is not focusable; the wrapped element keeps its own focus and keys.

**Known limits.**

- Whether the element is focusable is checked once it is in the page, so the label is read beside it until the page has loaded.
- A changed count is not announced. For a count that changes while people watch, announce it in a live region the page owns.
- The badge covers the element's corner. Keep at least 10 px of space around the element so the badge does not cover its neighbours.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### OverlayBadge

Renders a `span` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactElement<{ "aria-describedby"?: string \| undefined; }, string \| JSXElementConstructor<any>>` |  | One element: an icon, a button or an avatar. |
| `label` (required) | `string` |  | The badge in words, such as "3 unread messages". A focusable child is described by it; any other child has it read after it. The badge itself is hidden from assistive technology. |
| `count` | `number` |  | A number to show, in the provider's locale: the badge becomes a round count. |
| `dot` | `boolean` | `false` | An 8px dot instead of a count. |
| `max` | `number` |  | The largest count shown; above it the badge reads `{max}+` (the provider's `badgeOverflow`). |
| `severity` | `"secondary" \| "success" \| "info" \| "warning" \| "help" \| "danger" \| "contrast" \| "primary" \| null` |  | The badge's fill: `primary` (default), `secondary`, `success`, `info`, `warning`, `help`, `danger` or `contrast`. |
| `size` | `"default" \| "sm" \| "lg" \| null` |  | The badge's size: `sm`, `default` or `lg`. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="overlay-badge"` (OverlayBadge).

## Theming

The badge takes the solid tokens of its `severity` (see Badge). The ring is `--card`, the surface examples and cards use; on another surface, set it with `className` on the wrapper (`[&_[data-slot=overlay-badge-badge]]:outline-background`).

| Token | Used for |
| --- | --- |
| `--card` | outline |
