registry:ui · NativeHTML
badge — the eyebrow's inline cousin
A plain <span> with the site's micro-label voice: font-nav uppercase, tracked at 0.14em, a 1px hairline border, kbd-law geometry. Prominence is the variant ladder — fill, tonal (the bare default, brand-tinted), outline — and every status hue injects through the four global token slots, never a variant name. Seven universal axes resolve on the chip; the eighth, shape, is the family's own corner law.
$npx jixoai-ui add badge
https://ui.jixoai.com/r/badge.json
overview
Overview
Inline status metadata — versions, build states, counts — in the site grammar's micro-label voice. A display atom: it marks content, it is never the control.
The chip is a plain <span>: Share Tech Mono uppercase, tracked at
0.14em, a 1px hairline border, and the kbd-law geometry — height from the secondary
line, inline insets only, never block padding. It composes anywhere a span does:
inside headings, table cells, terminal cards.
The label is the children. Two optional icon lanes — slotStart and slotEnd — render a glyph at the secondary text size and halve their
side's inset so glyph and label stay balanced; an icon-only badge keeps both insets
symmetric so the glyph centers. Everything else a span carries lands verbatim: data-*, title, aria-*.
Two decisions are the author's. The surface — variant: fill (solid,
loudest), tonal (the 12% tinted default; a bare badge is the brand tint), or outline
(structure only). The hue — injection: statuses retarget the four global slots
(--jx-tonal and friends) through classes like jx-hue-error; the variant union never widens for
hue, and the arbitrary-property class covers anything outside the closed set.
Seven universal style axes resolve on the same chip — documented per-axis below;
the shared grammar lives on the universal props page. The
eighth axis, shape, is deliberately absent: the family's own shape prop (square | pill) owns the name. For an interactive filter
chip, use chip — a badge
marks content, it is never the control.
usage
Usage
The variant ladder plus the hue-injection recipes, live: the bare tonal reads as brand metadata, fill as the loudest voice, outline as quiet structure — and each status hue is one class on the same chip. The icon lane composes through slotStart; the drawer shows the exact registry copy this page runs.
badge
The chip composes anywhere a span does — headings, table cells, terminal cards.
<script lang="ts">
import Badge from '@ui/badge';
import Icon from '@ui/icon';
</script>
<div class={cx(rt.flex, rt.col, rt.itemsStart, rt.gap16)}>
<div class={cx(rt.wrapRow12)}>
<Badge>v1.2.0</Badge>
<Badge variant="fill">new</Badge>
<Badge variant="outline">beta</Badge>
</div>
<div class={cx(rt.wrapRow12)}>
<Badge class="jx-hue-neutral">draft</Badge>
<Badge class="jx-hue-error">failed</Badge>
<Badge shape="pill" class="jx-hue-success">
{#snippet slotStart()}<Icon name="check" />{/snippet}
passing
</Badge>
</div>
</div>props
Props
The table renders from the GENERATED meta; the eight universal axis rows split into the shared section beneath the family rows. shape is the family's own corner law, not the §2 axis — the census keeps the name and leaves the axis lane out.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| style | string | null | — | Inline style passthrough — composed AFTER the family's carrier stamp (the #4 seam law: never clobbered, never dropped). |
| variant | 'fill' | 'tonal' | 'outline' | 'tonal' · ambient zone · ambient zone | Prominence ladder: fill speaks loudest (solid ground, same-hue border, injected ink), tonal is the default voice (the 12% tinted ground, 45% border), outline draws structure only (transparent ground, the outline border). Hue comes from the global slots — never a variant name. Omitted → the ambient paint zone (ButtonGroup / zone scope), else the frozen own tonal. |
| slotStart | Snippet | — | Icon lane before the label — the svg sizes to the secondary text (var(--jx-text-secondary)) and its side's inset halves (the :has() lane) so glyph and label stay balanced. |
| slotEnd | Snippet | — | Icon lane after the label — same sizing and inset-halving law. An icon-only badge (no children) keeps BOTH insets symmetric so the glyph centers (the tabs-trigger guard, F-6). |
| class | string | '' | Consumer classes — also the hue-injection seam: jx-hue-neutral / jx-hue-error / jx-hue-success retarget the four global slots (--jx-fill, --jx-fill-ink, --jx-tonal, --jx-outline); the arbitrary-property class covers hues outside the closed set. |
| children | unknown | — | The label text — the chip's entire content; the chip renders it as bare span content (the chip itself is a plain <span>), composing anywhere one does: headings, table cells, terminal cards. |
| rest | unknown (spread passthrough) | — | Span attribute passthrough — data-*, title, aria-* land verbatim on the chip (a badge is a span; composition is the law, not a convenience). |
| shape | 'square' | 'pill' | 'square' · Own default, not ambient | The corner law: square keeps the theme radius (var(--jx-radius) — 0px; 8px where corner-shape is supported), pill rounds fully (calc(infinity * 1px)). A family-local literal slot, NOT the universal §2 shape axis — the vocabulary collides with the axis name and §13 rules no mapping, so the axis is absent (explicit-props migration-census.md, batch B LANDED row; flagged for the W6 Owner dossier). Rendered from extra: the shared split filters this name from the generated rows. |
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
Badge resolves seven universal axes through its family Defaults (BadgeDefaults) and stamps the resolved carriers on the chip root; the eighth — shape — is deliberately absent (the census row). What each axis drives HERE differs: density is the chip's scale (four kernel channels re-declare on the rung), theme is the one repainting axis (the hue slots re-declare on the .dark scope), size re-types the label while the kbd-law box stays density's — and three more are supply-only, recorded per axis instead of silently omitted.
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | Stamps --jx-size-effective AND an inline font-size: var(--jx-size-effective, 1rem) on the chip — the inline stamp out-ranks the family's class voice (fontSize: var(--jx-text-secondary)), so the micro-label re-types verbatim: a number in px, a named step through var(--jx-size-*) — 14 / 16 / 18px (measured). The kbd-law box does NOT follow: height and line ride --jx-line-secondary, density's channel, and stay fixed (a 12px → 24px label inside the same 20px box; measured). One axis, two winners — the label is size's, the box is density's. |
| shape | 'square' | 'pill' — the family prop | 'square' | Deliberately ABSENT as an axis: the family-local shape prop ('square' | 'pill', the corner-law silhouette) owns the name and pill is outside ShapeLane — §13 rules no mapping, so the lane is left out (forwarding ambient) rather than renamed (explicit-props migration-census.md, batch B LANDED row; flagged for the W6 Owner dossier). The corner law paints through the shape prop's row above — a documented absence, not a silent gap. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | Stamps --jx-radius-effective (a px number, or a named step through var(--jx-radius-*) — 6 / 8 / 10px, measured). The chip's own corner never reads the carrier: the square corner-law paints var(--jx-radius) — the theme's --radius (0px; 8px where corner-shape is supported; 8px measured here) — and pill paints calc(infinity * 1px). Supply-only: the anchor flows down, and a descendant at radius="auto" computes the concentric max(0px, R − inset) off it. |
| density | 'small' | 'medium' | 'large' | 'xs' | '2xs' | 'sm' | 'default' | 'lg' | 'auto' | number | 'auto' | The chip's scale axis. A named rung stamps data-density and re-declares the four kernel channels the atoms read ON the chip — --jx-text-secondary (the label), --jx-line-secondary (the height), --jx-inset, --jx-gap. Measured ladder: label 10 / 11 / 12 / 14px, line 12.5 → 21px (box 14.5 → 23px — line plus the 1px hairlines), insets 8 → 16px, 2xs → lg (small/medium/large alias sm/default/lg). A NUMBER lane stamps only --jx-density-coefficient — the channels declare and substitute at :root/[data-density] scopes, so a chip-level coefficient recomposes nothing (the declaring-element law; measured inert), and a named rung resets the coefficient to 1 (explicit rung = exact rung). |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | Stamps --jx-color-effective (named → var(--jx-color-*); a number = hue degrees through the oklch formula; a raw string passes verbatim). No badge-family css reads the carrier — the chip's paint is inert under the axis (measured same-instant). The real hue seam is the four global slots (--jx-fill / --jx-fill-ink / --jx-tonal / --jx-outline) retargeted by the jx-hue-* classes: inject hue through class, not this prop. Supply-only. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | The one axis that repaints the chip: dark stamps the .dark class bridge, and the four hue slots RE-DECLARE on the .dark scope (jixoai.css's :root, .jx-light, .dark selector list) — tonal and fill re-resolve against the dark profile, brand hue drifted −4° (measured: ground, ink and border all flip). One caveat, measured: the outline rung's ink reads --jx-foreground, a :root stylex token that does NOT re-substitute under a scoped .dark — the ink stays the light black (the W-next semantic-ink gap). system resolves the JS-mutable global; auto inherits the tree. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | Stamps --jx-elevation-effective (level-1…level5 are exact dp; a number snaps down to the enclosing rung). No badge-family css reads the carrier and the chip paints no shadow (measured: box-shadow none) — supply-only. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | Stamps --jx-motion-effective (named steps resolve var(--jx-motion-*); a coefficient verbatim). No badge-family css reads the carrier and the chip runs no transition (measured: transition-duration 0s) — supply-only. |
badge · on the axes
The consumed lanes, live: density rungs rescale the whole chip (label 10 → 14px, box 14.5 → 23px, measured), an explicit size re-types the label inside the fixed 20px box, the dark island flips the hue slots, and one real query() case steps the rung down at the 40rem viewport (resize the window).
<script lang="ts">
import Badge from '@ui/badge';
import { query } from '@lib/universal-props-query.svelte';
import type { DensityLane } from '@lib/defaults.svelte';
</script>
<div class={cx(rt.col16, rt.wFull)}>
<div class={cx(rt.gridSm2)}>
<div class={cx(rt.panel)}>
<Badge density="2xs">2xs rung — 10px label, 14.5px box (measured)</Badge>
</div>
<div class={cx(rt.panel)}>
<Badge density="lg">lg rung — 14px label, 23px box (measured)</Badge>
</div>
<div class={cx(rt.panel)}>
<Badge size={14}>size 14 — the label re-types, the box stays 20px</Badge>
</div>
<div class={cx(rt.panel)}>
<Badge theme="dark">dark island — the hue slots re-declare</Badge>
</div>
</div>
<div class={cx(rt.panel, rt.wFull)}>
<Badge density={query<{ sm: DensityLane }, DensityLane>({ sm: 'small' }, 'large')}>
responsive — the lg base below 40rem, the sm rung at 40rem and wider
</Badge>
</div>
</div>The supply-only rows are documented absences, not gaps: the chip's corners read
the family corner-law (--jx-radius, the theme's radius — or calc(infinity * 1px) for pill), its hue seam is the four global
slots, and it paints no shadow and runs no transition — so radius, color,
elevation, and motion stamp their carriers for descendants without repainting
this chip. The fleet classification and the shape-name collision are recorded in
explicit-props' migration-census.md (batch B LANDED rows).
| Token | Default | Source |
|---|---|---|
| --jx-text-secondary | 10 / 11 / 12 / 14px (2xs–xs / sm / default / lg) | density |
| --jx-line-secondary | 12.5 / 13.5 / 15.95 / 18 / 21px (2xs → lg) | density |
| --jx-inset | 8 / 12 / 16px (2xs–sm / default / lg) | density |
| --jx-gap | 8 / 12 / 16px (2xs–sm / default / lg) | density |
| --jx-fill | var(--primary) | color |
| --jx-fill-ink | var(--primary-foreground) | color |
| --jx-tonal | var(--primary) | color |
| --jx-outline | var(--border) | color |
| --jx-foreground | var(--foreground) at :root | structural |
| --jx-radius | var(--radius) — 0px (8px where corner-shape is supported) | structural |
| --jx-hairline | 1px | structural |
| --track-14 | 0.14em | structural |
a11y
Accessibility
A display atom, not a control: nothing to focus, nothing to operate — so the density hit floor that governs the interactive chip does not apply here; density moves the label rhythm only. The status must survive without hue: pair every color injection with wording, and under forced colors every rung degrades to Canvas ground + CanvasText ink with the 1px border intact.
Keyboard
| Key | Action |
|---|---|
| — | Not focusable — an inline display atom, not a control |
ARIA
| Attribute | Value | Description |
|---|---|---|
| span | inline status | No role — plain span semantics. Use visible text; do not rely on color alone (hue injection always pairs with wording). |
| aria-label | consumer supplied | Add context when the visible label is abbreviated (a bare +1 needs a title or aria-label). |
| data-jx-badge | variant | Hook attribute carrying the ladder variant (fill | tonal | outline) for styling and tests. |
| forced colors | Canvas / CanvasText | Every rung degrades to Canvas ground, CanvasText ink and border; the 1px frame survives (the atoms' @media (forced-colors: active) block). |