<!-- Protocore Design System — Indicator -->
# Indicator

- **Category:** Data Display (`data-display`)
- **Slug:** `data-display/indicator`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Indicator } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/data-display/indicator

> Wraps a child and pins a tone-coloured dot or count badge to a corner, with an optional live pulse.

## When to use it

`Indicator` is for **an overlay on something else** — a badge riding the corner of an avatar or icon button. When the status stands on its own line, use a **Badge** (a word) or a **StatusDot** (a dot beside a label) instead; both are cheaper and more legible than an overlay.

The overlay is decorative by design — it never carries an accessible name unless you give it one. Put the real meaning in the wrapped control's label ("Notifications, 7 unread") or pass `aria-label` on the Indicator. Position uses logical corners (`top-end`, `bottom-start`…) so it mirrors correctly under RTL.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `aria-label` | `string` | no | — | Accessible label for the overlay itself; when set it becomes `role="status"`. |
| `children` | `ReactNode` | no | — | The element the overlay decorates. |
| `className` | `string` | no | — | — |
| `count` | `number` | no | — | Numeric count. Renders inside the badge; `0` hides unless `showZero`. |
| `disabled` | `boolean` | no | `false` | Hide the overlay entirely (keeps the child mounted). |
| `label` | `ReactNode` | no | — | Arbitrary label content (a string/number) — an alternative to `count`. |
| `max` | `number` | no | `99` | Cap the count; larger values render as `{max}+`. |
| `position` | `enum` | no | `top-end` | Corner the overlay pins to. |
| `processing` | `boolean` | no | `false` | Softly pulse the overlay to signal a live/processing state. |
| `showZero` | `boolean` | no | `false` | Show the badge when `count` is `0`. |
| `size` | `enum` | no | `md` | Overlay size: sm 8 · md 10 · lg 12 (dot); grows to fit a label. |
| `style` | `CSSProperties` | no | — | — |
| `tone` | `enum` | no | `danger` | Overlay tone. |
| `withBorder` | `boolean` | no | `true` | Draw a canvas-coloured ring so the overlay reads clearly over the child. |

## Examples

### Dot

Wrap any element — an avatar, an icon button, a nav item — and `Indicator` pins a small sharp dot to a corner. With no `count` or `label` it's a bare presence dot: something is new here.

```tsx
import { Indicator, Avatar, Stack } from "@protocore/pds";

export default function IndicatorDot() {
  return (
    <Stack direction="row" gap={6} align="center">
      <Indicator tone="success" aria-label="Online">
        <Avatar name="Ada Lovelace" size="lg" />
      </Indicator>
      <Indicator tone="warning" position="bottom-end" aria-label="Away">
        <Avatar name="Grace Hopper" size="lg" />
      </Indicator>
      <Indicator tone="neutral" aria-label="Offline">
        <Avatar name="Alan Turing" size="lg" />
      </Indicator>
    </Stack>
  );
}
```

### Count & overflow

Pass a `count` to render a number badge. Counts above `max` (default 99) collapse to `{max}+`. A `count` of `0` hides the badge unless you set `showZero`. Because the overlay is decorative, carry the meaning in an `aria-label` ("7 unread") so it's announced.

```tsx
import { Indicator, IconButton, Stack } from "@protocore/pds";
import { Bell, Mail } from "lucide-react";

export default function IndicatorCount() {
  return (
    <Stack direction="row" gap={6} align="center">
      <Indicator count={7} aria-label="7 unread notifications">
        <IconButton variant="secondary" aria-label="Notifications">
          <Bell size={16} />
        </IconButton>
      </Indicator>
      <Indicator count={250} max={99} aria-label="99 or more messages">
        <IconButton variant="secondary" aria-label="Inbox">
          <Mail size={16} />
        </IconButton>
      </Indicator>
    </Stack>
  );
}
```

## Usage

**Do**

- Convey the count in the child's accessible name or the `aria-label`.
- Use `processing` only for live or streaming state.
- Keep `withBorder` on when the overlay sits over a busy child.

**Don't**

- Pair the colored overlay with a label; color alone doesn't convey status.
- For a standalone count, use a Badge.
- Use the ring for live signals, not static counts.

## Accessibility

**Notes**

- The overlay is `aria-hidden` decoration by default; give it an `aria-label` to promote it to an announced `role="status"`.
- Best practice is to keep the count in the wrapped control's own accessible name so it's read as one thing.
- The processing pulse is decorative and honours `prefers-reduced-motion` (it settles to its final frame).
- Corners are logical (start/end), so the overlay flips to the correct side under `dir="rtl"`.

## Related

`badge`, `status-dot`, `theme-icon`, `avatar`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
