jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

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.

install — badge-indicator

$npx jixoai-ui add badge-indicator

https://ui.jixoai.com/r/badge-indicator.json

dot · count · standalone 99+ overflow cap zero hides honestly the wrap re-tiers its tenant

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.

GB AL 5 99+ 0
badge-indicator-usage.svelte svelte
<!-- 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.

PropertyTypeDefaultDescription
dot dot + labelthe 10px primary dotannounces: role="img" · label ?? 'new activity'
count count 1…overflowthe 18px count chipannounces: the visible count text
capped count > overflow (99)'99+' — the cap, never the true numberannounces: '99+'
honest zero count={0}NOTHING — zero unread is no badgeannounces: nothing (absence is the truth)
opted zero count={0} showZerothe chip reading '0'announces: '0'
standalone no childrenthe same chip inline (position:static)announces: as its idiom dictates
usage
<!-- 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

GB

count — capped at 99+

AL 99+

standalone chip

5

showZero

0

usage

Usage

Anything with a corner carries the badge; omit children for the standalone chip.

BadgeIndicator usage
<!-- 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

PropertyTypeDefaultDescription
dot boolean—The presence idiom — beats count when only presence matters.
count number—The count idiom; hidden at 0 unless showZero.
overflow number99Cap before "n+" — the chip never shows the true number past the cap.
showZero booleanfalseRender 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

PropertyTypeDefaultDescription
size'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity 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.

PropertyTypeDefaultDescription
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.

the eight axes on badge-indicator
<!-- 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

5

density lg — tenant 48px, chip 18px

5

stamped lg on the chip — 18px stays

5

size 18 standalone — the inline mirror outruns the micro atom

5

color error + radius large — the paint does not move

8
badge-indicator-axes.svelte svelte
<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>
TokenDefaultSource
dot box10px = --jx-unit × 2.5structural
count chip18px = --jx-unit × 4.5structural
corner offsets±6px = --jx-unit × 1.5structural
chip text--jx-text-micro (10px)structural
chip frame--jx-hairline + --jx-backgroundstructural
chip corner--jx-radius (frozen)structural
--jx-text / --jx-hit / --jx-insetkernel rung channelsdensity
xs
GB AL 12 99+ 5
sm
GB AL 12 99+ 5
default
GB AL 12 99+ 5
lg
GB AL 12 99+ 5

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

KeyAction
—Not focusable — the wrapped child keeps its own interaction; the overlay is never a stop

ARIA

AttributeValueDescription
roleimgOn the dot only — presence with no text content of its own.
aria-labellabel ?? 'new activity' (dot) · the count textThe dot announces its label; a count chip announces its visible text ("99+" included).
no aria-liverulingThe 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 zeroabsencecount=0 renders nothing — zero unread announces nothing at all, which is the truth; showZero opts into announcing "0".
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h