registry:ui · General
badge-indicator — the live count/dot overlay
antd Badge's live half, split from the static chip (badge.svelte): the count/dot rides a corner of its child. dot is presence without a number — label carries the accessible name; count caps at 99+ and zero hides honestly unless showZero. Standalone (no child) is an inline chip; the wrap is a live density scope for whatever it carries.
$npx jixoai-ui add badge-indicator
https://ui.jixoai.com/r/badge-indicator.json
overview
Overview
A positional overlay with two deterministic idioms — and a wrap that scopes what it carries.
badge-indicator is antd Badge's OTHER half: where badge is the static
status chip, this family renders the live indicator riding a corner of its child (an
avatar, an icon button, a tab). Two idioms, each one complete paint: the dot — a 10px primary presence box with no number — and the count — an 18px destructive number box capped by overflow (default 99+). With no child the same chip renders standalone, inline.
Composition is the family's reason to exist, and it is REAL in the eight-axis sense:
the wrapper is position: relative for the corner offsets AND a live
density scope for its tenant — an explicit rung re-bases the kernel channels inside
the wrap, so a button under a density="lg" indicator measures the lg
tier. Kinship, named precisely: badge is the static chip, chip is the badge's activation twin (its body geometry verbatim) — and
the three share only the micro-scale vocabulary, never imports (grep receipt:
badge-indicator imports nothing from either).
The chip's own paint is the fixed micro scale: dot 10px and count 18px are unit
equations off the never-re-based --jx-unit, the text is the typed micro
step, and the frame is the hairline over the background token. Every universal axis is
no-own — stamped and broadcast, read by no family atom — and theme is the frozen pole
measured: inside a dark island the count chip keeps its black ground while the theme
sheet's raw destructive flips to white. The one voice that moves is the §11 carrier's
own echo: standalone, the inline font-size mirror lands on the chip itself (the size
row). Per-axis below.
badge-indicator
Three postures plus the honest zero. The demo children are plain spans — anything with a corner carries the badge.
<!-- dot = presence without a number; label is the a11y name -->
<BadgeIndicator dot label="2 unread">
<Avatar name="Grace" />
</BadgeIndicator>
<!-- count caps at 99+; zero hides honestly -->
<BadgeIndicator count={5}>
<button type="button">inbox</button>
</BadgeIndicator>
<!-- standalone (no child) is an inline chip -->
<BadgeIndicator count={250} />law
Presence rules
The overlay is a positional wrapper, not a semantic one: the badge rides the child's corner as decoration, and the label/count text carries the meaning. Zero is the absence of news — it renders nothing unless showZero says otherwise.
| Property | Type | Default | Description |
|---|---|---|---|
| dot | dot + label | the 10px primary dot | announces: role="img" · label ?? 'new activity' |
| count | count 1…overflow | the 18px count chip | announces: the visible count text |
| capped | count > overflow (99) | '99+' — the cap, never the true number | announces: '99+' |
| honest zero | count={0} | NOTHING — zero unread is no badge | announces: nothing (absence is the truth) |
| opted zero | count={0} showZero | the chip reading '0' | announces: '0' |
| standalone | no children | the same chip inline (position:static) | announces: as its idiom dictates |
<!-- dot = presence without a number; label is the a11y name -->
<BadgeIndicator dot label="2 unread">
<Avatar name="Grace" />
</BadgeIndicator>
<!-- count caps at 99+; zero hides honestly -->
<BadgeIndicator count={5}>
<button type="button">inbox</button>
</BadgeIndicator>
<!-- standalone (no child) is an inline chip -->
<BadgeIndicator count={250} />types
Postures
Three postures — dot, count, standalone — plus the honest zero and its opt-in.
dot — presence, no number
GBcount — capped at 99+
AL 99+standalone chip
5showZero
0usage
Usage
Anything with a corner carries the badge; omit children for the standalone chip.
<!-- dot = presence without a number; label is the a11y name -->
<BadgeIndicator dot label="2 unread">
<Avatar name="Grace" />
</BadgeIndicator>
<!-- count caps at 99+; zero hides honestly -->
<BadgeIndicator count={5}>
<button type="button">inbox</button>
</BadgeIndicator>
<!-- standalone (no child) is an inline chip -->
<BadgeIndicator count={250} />api
API
Six documented props over the eight-lane surface — dot beats count, zero hides honestly; class forwards to the wrapper/standalone chip unseen.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| dot | boolean | — | The presence idiom — beats count when only presence matters. |
| count | number | — | The count idiom; hidden at 0 unless showZero. |
| overflow | number | 99 | Cap before "n+" — the chip never shows the true number past the cap. |
| showZero | boolean | false | Render count=0 instead of hiding. |
| children | Snippet | — | What the indicator rides on; omitted = standalone chip. The wrap is a live density scope for the child. |
| label | string | 'new activity' | Accessible name for the dot (role="img"); counts announce their visible text. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |
axes
The eight axes on badge-indicator
All no-own, census batch B — and the chip's paint hears almost none of it: dot 10px and count 18px are unit equations off the never-re-based --jx-unit, the text is the typed micro step, the hues are the idioms' own (primary / destructive). Density's one live wire is the WRAP, a real density scope for its tenant; size's one voice is the §11 mirror's inline echo on the standalone chip; theme is the frozen pole measured across the bridge — typed chip black while raw destructive flips white. shape / radius / color / elevation / motion stamp-and-supply to nothing (grep receipts).
Reading the table: Property is the axis, Type is the real carrier or consumption it drives on THIS family, Default is the lane default — the named steps, number unit, and consumption are in each description.
| Property | Type | Default | Description |
|---|---|---|---|
| density | 'small' | 'medium' | 'large' | 'auto' | number (+ legacy spellings) | 'auto' | BROADCAST-AND-SCOPE, INERT ON THE CHIP — the rung stamps data-density and the carriers broadcast, but the chip's geometry rides unit equations off --jx-unit (one :root declaration, never re-based): a stamped lg leaves the dot 10px and the count 18px (probe receipt). The live wire is the WRAP: an explicit rung is a real density scope for the tenant it carries — the kernel channels re-base inside it (--jx-text resolves to the lg composition), so a PressButton under a density="lg" wrap measures 48px against 40px beside it. No query() seat: the chip consumes nothing and a responsive seat is a tenant composition recipe, not a family axis (the code-card judged-sound precedent, stated here per the same rule). |
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | THE §11 ECHO, POSTURE-SPLIT — the stamp lands verbatim (--jx-size-effective: 18px; font-size: var(--jx-size-effective, 1rem) on the root). RIDING A CHILD, the mirror lands on the wrap and nothing follows: the chip's own typed micro atom (--jx-text-micro, 10px) outranks inheritance (measured 10px under an 18px mirror). STANDALONE, the chip IS the root — the inline mirror beats the class atom (the merge law's inline-over-atom precedence, the avatar-echo class) and the micro text follows size (measured 18px). Number unit: px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | SUPPLY-ONLY — stamps --jx-shape-effective/--jx-radius-factor-effective; zero shape reads in the family atoms (grep receipt). The silhouette is the radius law, not the axis. Number unit: none. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | SUPPLY-ONLY — the corner is tokens['--jx-radius'], the FROZEN theme radius (measured 8px, byte-identical under radius="large"): --jx-radius-effective is stamped and unread. Number unit: px. |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | SUPPLY-ONLY — the idioms OWN their hues: the dot paints --jx-primary, the count chip paints the destructive pair (--jx-destructive / --jx-destructive-foreground); --jx-color-effective is stamped and unread (grep receipt; a color="error" chip stays destructive — measured). Number unit: hue degrees. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | TYPED-FROZEN (the frozen-pole family): the class:dark bridge lands on the root and nothing reads it — every painted voice is a typed :root emission. Measured across the bridge: inside a .dark island the count chip keeps its black ground and white ink while the theme sheet's RAW --destructive flips to oklch(1 0 0) — the typed read freezes at the :root substitution, the raw read follows the tree. That contrast is the whole mechanism, one shadow token apart. No number lane. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | SUPPLY-ONLY — the overlay floats by position, not shadow: zero box-shadow in the family atoms (grep receipt); the hairline frame + the background border do the lifting. Number unit: dp. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | SUPPLY-ONLY — no transition, no kernel read in the family atoms (grep receipt): the indicator is static state, and appearance/disappearance is rendered truth, not choreography. Number unit: coefficient. |
Deviations, cited: the adoption is the census batch B row (explicit-props W3 batch B / task 3.2 — openspec/changes/explicit-props/research/migration-census.md). No-rest note: the interface closes at class — there is no rest spread, so no synthesized rest row applies (the no-rest-spread check). The old page's token rows for --jx-icon and --jx-inset documented density channels this family has never read; they retire with the measured table.
<!-- all no-own: the chip's paint is the fixed micro scale -->
<BadgeIndicator dot label="live" size={18} density="small" />
<BadgeIndicator count={120} size="large" radius="large" color="error" />
<!-- the one live wire: the WRAP re-tiers its tenant -->
<BadgeIndicator count={5} density="lg">
<PressButton>inbox</PressButton>
</BadgeIndicator>badge-indicator · the wrap re-tiers its tenant; the chip hears nothing
density default — tenant 40px
5density lg — tenant 48px, chip 18px
5stamped lg on the chip — 18px stays
5size 18 standalone — the inline mirror outruns the micro atom
5color error + radius large — the paint does not move
8<script lang="ts">
import BadgeIndicator from '@ui/badge-indicator.svelte';
import PressButton from '@ui/press-button.svelte';
</script>
<div class={cx(rt.gridSm2, rt.wFull)}>
<div class={cx(rt.panel)} data-probe="wrap-default">
<p class={cx(rt.eyebrow, rt.mb12, rt.inkMuted)}>density default — tenant 40px</p>
<BadgeIndicator count={5} density="default">
<PressButton>inbox</PressButton>
</BadgeIndicator>
</div>
<div class={cx(rt.panel)} data-probe="wrap-lg">
<p class={cx(rt.eyebrow, rt.mb12, rt.inkMuted)}>density lg — tenant 48px, chip 18px</p>
<BadgeIndicator count={5} density="lg">
<PressButton>inbox</PressButton>
</BadgeIndicator>
</div>
<div class={cx(rt.panel)} data-probe="stamped-chip">
<p class={cx(rt.eyebrow, rt.mb12, rt.inkMuted)}>stamped lg on the chip — 18px stays</p>
<BadgeIndicator count={5} density="lg" />
</div>
<div class={cx(rt.panel)} data-probe="size-echo">
<p class={cx(rt.eyebrow, rt.mb12, rt.inkMuted)}>size 18 standalone — the inline mirror outruns the micro atom</p>
<BadgeIndicator count={5} size={18} />
</div>
<div class={cx(rt.panel)} data-probe="supply-only">
<p class={cx(rt.eyebrow, rt.mb12, rt.inkMuted)}>color error + radius large — the paint does not move</p>
<BadgeIndicator count={8} radius="large" color="error" />
</div>
</div>| Token | Default | Source |
|---|---|---|
| dot box | 10px = --jx-unit × 2.5 | structural |
| count chip | 18px = --jx-unit × 4.5 | structural |
| corner offsets | ±6px = --jx-unit × 1.5 | structural |
| chip text | --jx-text-micro (10px) | structural |
| chip frame | --jx-hairline + --jx-background | structural |
| chip corner | --jx-radius (frozen) | structural |
| --jx-text / --jx-hit / --jx-inset | kernel rung channels | density |
a11y
Accessibility
The overlay is decoration riding the child; the meaning lives in the label/count text. The indicator paints STATE, not events — liveness belongs to the host.
Keyboard
| Key | Action |
|---|---|
| — | Not focusable — the wrapped child keeps its own interaction; the overlay is never a stop |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role | img | On the dot only — presence with no text content of its own. |
| aria-label | label ?? 'new activity' (dot) · the count text | The dot announces its label; a count chip announces its visible text ("99+" included). |
| no aria-live | ruling | The indicator paints state, not events: a count change is silent by design. When the change itself matters, the HOST owns the live region — compose one around the child; the overlay stays decoration. |
| honest zero | absence | count=0 renders nothing — zero unread announces nothing at all, which is the truth; showZero opts into announcing "0". |