/// Data Display
Indicator
Wraps a child and pins a tone-coloured dot or count badge to a corner, with an optional live pulse.
import { Indicator } from "@protocore/pds";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.
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.
Processing pulse
Add processing to emit a soft expanding ring — a genuinely live signal for a syncing avatar or a running job. Reserve it for real-time state; the animation respects the platform's reduced-motion preference.
Reindexing the ledger — live
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.
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
- 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"`.
Indicator props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible label for the overlay itself; when set it becomes `role="status"`. |
children | ReactNode | — | The element the overlay decorates. |
className | string | — | |
count | number | — | Numeric count. Renders inside the badge; `0` hides unless `showZero`. |
disabled | boolean | false | Hide the overlay entirely (keeps the child mounted). |
label | ReactNode | — | Arbitrary label content (a string/number) — an alternative to `count`. |
max | number | 99 | Cap the count; larger values render as `{max}+`. |
position | enum | top-end | Corner the overlay pins to. |
processing | boolean | false | Softly pulse the overlay to signal a live/processing state. |
showZero | boolean | false | Show the badge when `count` is `0`. |
size | enum | md | Overlay size: sm 8 · md 10 · lg 12 (dot); grows to fit a label. |
style | CSSProperties | — | |
tone | enum | danger | Overlay tone. |
withBorder | boolean | true | Draw a canvas-coloured ring so the overlay reads clearly over the child. |