# Badge

> Inline labels, counters and status dots from one data-badge attribute.

Page: https://pico.rusticstack.com/docs/extras/badge

A badge is an inline label, counter or status tag. One hook, `data-badge`, goes on a `span`, `data`, `a` or `button`, and its value picks the color. The badge scales with the font size of its parent, so it fits in a heading, a paragraph or a `small`.

Load `css/extras/badge.min.css`, or the whole set from `css/pico.extras.min.css`. See the [overview](/docs/extras/overview).

## Variants

The values are `secondary`, `contrast`, `success`, `warning` and `danger`. An empty value (just `data-badge`) is the primary color.

**Example: Badge variants**

```html
<p>
  <span data-badge>Default</span>
  <span data-badge="secondary">Secondary</span>
  <span data-badge="contrast">Contrast</span>
  <span data-badge="success">Active</span>
  <span data-badge="warning">Pending</span>
  <span data-badge="danger">Failed</span>
</p>
```

## Counters

Use `<data>` with a `value` for a number, so the machine readable value travels with the text. Hide a count of zero with the `hidden` attribute.

**Example: Counters**

```html
<p>
  <data value="1" data-badge>1</data>
  <data value="9" data-badge="secondary">9</data>
  <data value="42" data-badge="danger">42</data>
  <data value="1280" data-badge="contrast">1280</data>
  <data value="99" data-badge="success">99+</data>
</p>
```

Updating a counter needs a few lines of script. The text node after the hidden context span (see [In buttons and links](#in-buttons-and-links)) is the visible count, so only that node changes. Without a context span, set `el.textContent` instead.

```js
function setBadge(el, n) {
  el.value = String(n);
  el.lastChild.textContent = String(n);
  el.hidden = n === 0;
}
```

Set `data-badge` at render time. Do not switch the color from script for a static state. For a count that screen reader users should hear, put `aria-live="polite"` on the `data` element only, never on the button.

## Dots

A badge with no text is a dot. It is decorative, so hide it from assistive technology with `aria-hidden="true"` and let the text next to it carry the meaning.

**Example: Status dots**

```html
<ul>
  <li><span data-badge="success" aria-hidden="true"></span> API operational</li>
  <li><span data-badge="warning" aria-hidden="true"></span> Webhooks degraded</li>
  <li><span data-badge="danger" aria-hidden="true"></span> Billing sync offline</li>
</ul>
```

## In buttons and links

On a solid button the default, secondary and contrast fills invert so they stay visible. Status colors keep their own fill and gain a ring in the button text color. An outline button has no fill, so its badges apply as they are.

Every direct child `span` of a badge is visually hidden but still read by screen readers. That is how you write context, like "unread messages: " before the count. To put an icon or styled text inside a badge, wrap it in another element such as `<i>` or `<b>`.

**Example: Badges in buttons**

```html
<p>
  <button type="button">
    Inbox
    <data value="4" data-badge><span>unread messages: </span>4</data>
  </button>
  <button type="button" class="secondary">
    Drafts
    <data value="2" data-badge="secondary"><span>drafts: </span>2</data>
  </button>
  <button type="button" class="contrast">
    Approvals
    <data value="7" data-badge="contrast"><span>pending: </span>7</data>
  </button>
  <button type="button">
    Failed jobs
    <data value="3" data-badge="danger"><span>failed: </span>3</data>
  </button>
  <button type="button" class="outline">
    Notifications
    <data value="5" data-badge><span>new: </span>5</data>
  </button>
</p>
<p>
  <a href="#releases" role="button">Releases <span data-badge="success">New</span></a>
  <a href="#changelog">Changelog <span data-badge>v2.4</span></a>
</p>
```

The badge can also be the control: `<button type="button" data-badge>Filter</button>` or `<a href="#tag" data-badge="secondary">Tag</a>`. A link badge does not underline on hover.

## In text and tables

**Example: Badges in headings and tables**

```html
<h3>Billing webhooks <span data-badge="warning">Beta</span></h3>
<p>
  Plan: Team <span data-badge="contrast">Trial, 9 days left</span>. Invoice #2043
  <span data-badge="danger">Overdue</span>.
</p>

<table>
  <thead>
    <tr>
      <th scope="col">Customer</th>
      <th scope="col">Plan</th>
      <th scope="col">Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Northwind Traders</th>
      <td>Business</td>
      <td><span data-badge="success">Active</span></td>
    </tr>
    <tr>
      <th scope="row">Globex Corporation</th>
      <td>Team</td>
      <td><span data-badge="warning">Payment pending</span></td>
    </tr>
    <tr>
      <th scope="row">Initech</th>
      <td>Starter</td>
      <td><span data-badge="danger">Suspended</span></td>
    </tr>
  </tbody>
</table>
```

## Accessibility

- A badge never relies on color alone. Put the state in the text ("Suspended"), or next to a dot.
- Hide text-less dots with `aria-hidden="true"`.
- Use a context `span` inside a counter so "4" is read as "unread messages: 4".
- Under forced colors a badge gets a 1px border, so its shape stays visible.

## Customization

| Variable | Default | Description |
| --- | --- | --- |
| `--pico-badge-background-color` | `var(--pico-primary-background)` | Fill of the default badge. Each variant sets it. |
| `--pico-badge-color` | `var(--pico-primary-inverse)` | Text color of the default badge. |
| `--pico-badge-success-background-color` |  | Fill of the success variant. |
| `--pico-badge-success-color` |  | Text color of the success variant. |
| `--pico-badge-warning-background-color` | `oklch(0.7408 0.1521 83.91)` | Fill of the warning variant. |
| `--pico-badge-warning-color` |  | Text color of the warning variant. |
| `--pico-badge-danger-background-color` |  | Fill of the danger variant. |
| `--pico-badge-danger-color` |  | Text color of the danger variant. |

