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

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

> A small sharp square that encodes a status tone, with an optional live pulse.

## When to use it

StatusDot is the most compact way to show a health/status tone — right for tight rows and grids where a full **Badge** would be too heavy. Reach for a **Badge** instead when you have room and want a legible word ("Degraded") rather than just a coloured square; reach for **MetricDelta** when the thing you're signalling is a *direction of change*, not a state.

The hard rule: a StatusDot never communicates on colour alone. Either it sits beside a text label, or it carries an `aria-label`. Two greens that mean different things must still differ in their adjacent text.

## Mobile (React Native)

**Preview.** `@protocore/pds-mobile` ships the React Native sibling of **StatusDot**. It mirrors the web API where React Native allows; the package is a **preview** with no device-level QA yet, so pin it and expect small changes.

Import it from the mobile package (not `@protocore/pds`), inside a `<PdsProvider>` — there is no stylesheet, so `style` (a `ViewStyle`) replaces `className` and every value comes from the theme:

```tsx
import { StatusDot } from "@protocore/pds-mobile";
```

**Parity with web.** An 8dp sharp tone indicator with an optional pulse.

- The pulse is an `Animated` opacity loop that honours the OS "reduce motion" setting (web uses a CSS keyframe gated by `prefers-reduced-motion`).
- `accessibilityLabel` is required when the dot stands alone, so status is never color-only; it sets `accessibilityRole="image"`.

```tsx
<StatusDot tone="success" pulse accessibilityLabel="Service healthy" />
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `aria-label` | `string` | no | — | Accessible label. **Required when the dot stands alone** (no adjacent text label), so status is never conveyed by color only. |
| `className` | `string` | no | — | — |
| `pulse` | `boolean` | no | `false` | When true, the dot softly pulses to signal a live/active state. |
| `style` | `CSSProperties` | no | — | — |
| `tone` | `enum` | no | `neutral` | Status tone that fills the dot. |

## Examples

### Basics

A StatusDot next to a text label is the canonical pattern: the label carries the meaning, the dot gives it a fast colour cue. Five tones, matching the Badge palette.

```tsx
import { StatusDot, Stack, Text } from "@protocore/pds";

// A dot paired with a text label is the canonical use — the label carries the
// meaning, the dot gives it a fast colour cue.
export default function StatusDotBasics() {
  return (
    <Stack direction="column" gap={2}>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="success" />
        <Text as="span" size="sm">
          Healthy
        </Text>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="warning" />
        <Text as="span" size="sm">
          Degraded
        </Text>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="danger" />
        <Text as="span" size="sm">
          Offline
        </Text>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="neutral" />
        <Text as="span" size="sm">
          Unknown
        </Text>
      </Stack>
    </Stack>
  );
}
```

### Live pulse

Add `pulse` to softly animate the dot for a genuinely live or active state — a streaming feed, an in-progress reindex. Reserve it for real-time signals so the animation stays meaningful.

```tsx
import { StatusDot, Stack, Text } from "@protocore/pds";

// `pulse` softly animates the dot to signal a live/active stream — reserve it
// for genuinely real-time state so it stays meaningful.
export default function StatusDotLive() {
  return (
    <Stack direction="column" gap={2}>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="success" pulse />
        <Text as="span" size="sm">
          Streaming events
        </Text>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="info" pulse />
        <Text as="span" size="sm">
          Reindexing
        </Text>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <StatusDot tone="neutral" />
        <Text as="span" size="sm">
          Idle
        </Text>
      </Stack>
    </Stack>
  );
}
```

## Usage

**Do**

- Pair the dot with a text label wherever space allows.
- Give a standalone dot an `aria-label` describing the status in words.
- Reserve `pulse` for truly live states so it keeps its signal value.
- Keep tone→meaning consistent with Badge across the product.

**Don't**

- Don't rely on colour alone — no label and no `aria-label` is a failure.
- Don't pulse static states; a page full of pulsing dots reads as noise.
- Don't use a dot where a word would be clearer and there's room for a Badge.
- Don't invent tones beyond the five semantic ones.

## Accessibility

**Notes**

- When given an `aria-label`, StatusDot renders with `role="img"` so assistive tech announces the status as a labelled graphic.
- Without a label it is presentational — that mode is only valid when an adjacent visible text label already states the status.
- The `pulse` animation is purely decorative; it conveys no information beyond the tone + label and respects the platform's reduced-motion preference.

## Related

`badge`, `table`, `timeline`, `tag`

---

© 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/
