# Badge

> A count or a dot pinned to the corner of whatever it wraps.

A badge wraps something and pins a marker to its top corner: unread messages on an inbox button, an online dot on an avatar. It takes the thing it marks as children.

## Basic badge

```javascript
stack({ direction: 'row', gap: 'xl', align: 'center' },
  badge({ content: 4 }, button({ variant: 'soft', color: 'neutral' }, 'Inbox')),
  badge({ content: 12 }, avatar({ name: 'Ada Lovelace' })),
)
```

## Colors

```javascript
stack({ direction: 'row', gap: 'xl', align: 'center', wrap: true },
  badge({ content: 3, color: 'primary' }, button({ variant: 'soft', color: 'neutral' }, 'Primary')),
  badge({ content: 3, color: 'neutral' }, button({ variant: 'soft', color: 'neutral' }, 'Neutral')),
  badge({ content: 3, color: 'success' }, button({ variant: 'soft', color: 'neutral' }, 'Success')),
  badge({ content: 3, color: 'warning' }, button({ variant: 'soft', color: 'neutral' }, 'Warning')),
  badge({ content: 3, color: 'danger' }, button({ variant: 'soft', color: 'neutral' }, 'Danger')),
)
```

## Maximum

A count above `max` renders as `n+`, so a badge never grows wide enough to unbalance the thing it sits on.

```javascript
stack({ direction: 'row', gap: 'xl', align: 'center', wrap: true },
  badge({ content: 9 }, button({ variant: 'soft', color: 'neutral' }, 'Nine')),
  badge({ content: 250 }, button({ variant: 'soft', color: 'neutral' }, 'Capped at 99')),
  badge({ content: 250, max: 999 }, button({ variant: 'soft', color: 'neutral' }, 'max: 999')),
)
```

## Dot

A dot says "something changed" without saying how much. Give it a `label` — a bare dot means nothing to a screen reader, so without one it is hidden from the accessibility tree entirely.

```javascript
stack({ direction: 'row', gap: 'xl', align: 'center' },
  badge({ dot: true, color: 'success', label: 'Online' }, avatar({ name: 'Ada Lovelace' })),
  badge({ dot: true, color: 'warning', label: 'Needs attention' },
    iconButton({
      label: 'Settings',
      variant: 'soft',
      color: 'neutral',
      icon: icon('settings'),
    }),
  ),
)
```

## Labelling the count

A bare number is ambiguous out of context. `label` becomes the badge’s accessible name, so it reads as "4 unread messages" rather than "4".

```javascript
badge({ content: 4, label: '4 unread messages' },
  button({ variant: 'soft', color: 'neutral' }, 'Inbox'),
)
```

## Changing the count

A count is the number on a page most likely to change while it is open. `setBadge()` clamps it to `max` the way the server did, keeps the announced text with it, and drops an emptied badge out of the accessibility tree — which is how a badge disappears.

The announced text is the site’s prose, so pass it whenever the badge has one:

`Anywhere`

```javascript
button({ onclick: "import('/su/badge.js').then(m=>m.set('inbox',0))" }, 'Mark all read')
```

Or from your own module, when there is already one running:

`src/main.js`

```javascript
import { setBadge } from 'sitelo/ui/client'

setBadge('inbox', 7, { label: '7 unread messages' })
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `content` | `string \| number` | — | What the badge shows. Ignored when dot is set. |
| `color` | `'primary' \| 'neutral' \| 'success' \| 'warning' \| 'danger'` | `'danger'` | Badge colour. |
| `dot` | `boolean` | `false` | A small dot instead of a value. |
| `max` | `number` | `99` | Counts above this render as n+. |
| `label` | `string` | — | Accessible name for the badge itself. |
