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

registry:ui · NativeHTML

progress — the element, painted

W3C-first: progress IS the native <progress> element. Role, value semantics, min/max mapping and the indeterminate state all belong to the browser. The component adds only the jixoai paint — 1px frame, brand fill, a terminal stripe sweep for the indeterminate run — and an optional label with a polite % readout.

install — progress

$npx jixoai-ui add progress

https://ui.jixoai.com/r/progress.json

native <progress> indeterminate built-in two live clocks, measured polite % readout

overview

Overview

The native element carries the semantics; the family carries two clocks, one seam, and a polite readout — all measured.

The semantics are the platform's, and the family adds nothing to them: a native <progress> exposes the progressbar role and its value mapping natively (indeterminate simply omits the current value — measured position −1 with no valuenow), the fallback content inside the element serves ancient engines, and zero ARIA wiring is authored. The component paints the bar (appearance reset across both engines, a 1px hairline frame over a muted track, brand fill at a 10px height and a 4px corner seam), and adds a label + live % readout.

There are TWO clocks, and both are live. The INDETERMINATE clock: a terminal-stripe sweep at var(--motion-indeterminate, 900ms) linear, a 24px tile — measured 0.9s/linear/infinite, slowing to 4s under prefers-reduced-motion. The VALUE clock: the fill's authored 200ms decelerate is live — burst-measured (clip frames decoded in-page), the fill edge is mid-tween at ~35ms and settles inside ~150ms of paint, not a first-frame jump — and the tween PERSISTS under reduced-motion: the css kill wins the cascade, yet the engine's own smoothing keeps the visible motion (css-unreachable — a reduced-motion user still sees the fill animate). Instrument note, carried honestly: a single capture taken ≥200ms after the set sees two settled bars and reads "inert" — burst capture is the truth instrument for paint transitions. The % readout is role=status: polite — announced when the reader is idle, never interrupting.

The eight axes are near-zero by ruling (value/max/label are data semantics): density is managed but the paint is density-invariant (measured 10px at every rung — the height equation reads the fixed 4px unit); the radius corner rides the own 4px promotion seam; theme is the frozen-ink seam's third instance — the fill re-derives with the brand chain while the track, frame and readout inks keep their light literals; size echoes on the root; shape/color/elevation/motion supply unread. Kinship: spin (the activity twin for spaces a bar cannot fit) and timeline (the position-on-a-scale cousin).

progress

A determinate bar driven live from the playground, and an indeterminate one below — same element, no value attribute.

deploy 42%
42%
connecting
progress-usage.svelte svelte
<Progress {value} label="deploy" />

W3C foundation

What the platform gives

The element exposes aria-valuenow/min/max natively (indeterminate simply omits valuenow), renders fallback content for ancient engines, and needs no ARIA wiring from us. The paint resets both engines' ::-progress pseudo-elements so WebKit and Gecko draw the same bar.

usage
<script lang="ts">
  import Progress from '@ui/progress.svelte';
</script>

<Progress value={0.42} label="sync" />      <!-- 42% -->
<Progress value={done} max={total} />       <!-- your scale -->
<Progress />                                <!-- indeterminate: activity, not progress -->

types

Types

Two states, one element: determinate maps a 0..max scale; omitted value means indeterminate.

progress · states

determinate
sync 42%
42%
value present — a real 0..max position with a % readout
indeterminate
connecting
value omitted — activity, not progress (terminal stripe sweep)
progress-types-demo.svelte svelte
<script lang="ts">
  import Progress from '@ui/progress.svelte';
</script>

<div class="flex flex-wrap items-start gap-6">
  <div class="flex min-w-56 flex-col gap-3 border border-border p-4"><span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">determinate</span><Progress value={0.42} label="sync" /><span class="text-muted-foreground text-[12.5px]">value present — a real 0..max position with a % readout</span></div>
  <div class="flex min-w-56 flex-col gap-3 border border-border p-4"><span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">indeterminate</span><Progress label="connecting" /><span class="text-muted-foreground text-[12.5px]">value omitted — activity, not progress (terminal stripe sweep)</span></div>
</div>

usage

Usage

Pass a 0..max value, or omit it for the honest 'something is happening' state.

Progress usage
<script lang="ts">
  import Progress from '@ui/progress.svelte';
</script>

<Progress value={0.42} label="sync" />      <!-- 42% -->
<Progress value={done} max={total} />       <!-- your scale -->
<Progress />                                <!-- indeterminate: activity, not progress -->

theming

Density and tokens

The bar's paint is density-INVARIANT — the height equation reads the fixed 4px unit (measured 10px at every rung, floor included) — and its clocks ride promotion seams: the sweep's 900ms linear and the fill's corner seam are reported constants a consumer var can re-shape.

2xs
sync 42%
42%
xs
sync 42%
42%
sm
sync 42%
42%
default
sync 42%
42%
lg
sync 42%
42%
TokenDefaultSource
brand fillvar(--primary)color
--progress-radius4pxcomponent
--motion-indeterminate900msstructural
--motion-linearlinearstructural
--jx-unit0.25remdensity

api

API

Three data props and the class passthrough. The generated meta carries 12 NAMED entries (value, max, label, the eight axes, class) and no rest key — the component is rest-less: undeclared attributes are dropped, not spread, and the element's role and value semantics are native, not forwarded. The universal fold serves the eight axis rows.

Properties

PropertyTypeDefaultDescription
value number—0..max; omitted ⇒ indeterminate ("activity", not "progress").
max number1The element's own spec default.
label string—Visible label above the bar (also names the element via aria-label; without one the bar falls back to the generic name "progress").
class string''Class passthrough to the root.

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 progress

The zero-hit family, measured honestly: density is managed but the paint is density-INVARIANT (10px at every rung — the height equation reads the fixed unit); the corner rides the own 4px promotion seam (axis unread); theme is the frozen-ink seam's third instance — the brand fill re-derives while track, frame and readout inks stay light literals; size echoes on the root; shape/color/elevation/motion supply unread. Two live clocks, both off the axis: the 0.9s stripe sweep and the fill's burst-measured tween that persists under reduced-motion.

PropertyTypeDefaultDescription
density '2xs' | 'xs' | 'sm' | 'default' | 'lg' | 'auto' | number (+ the five legacy spellings)'auto'MANAGED BUT PAINT-INVARIANT — the rung attr rides the root and the channels stay open for composed descendants, but the bar's own paint is density-invariant: the height equation calc(var(--jx-unit) * 2.5) reads the FIXED 4px unit (measured 10px at every rung including the 2xs stamp), and the readout voices ride promoted label steps (12px, measured constant). The zero-hit ruling stands — value/max/label are data semantics. Number unit: coefficient.
size 'small' | 'medium' | 'large' | 'auto' | number'auto'ROOT ECHO — the §11 stamp scales the field root (the readout head rides the root's font), while the native bar keeps its 10px equation. Zero --jx-size-effective readers (grep receipt). Number unit: px.
shape 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto'SUPPLY-ONLY — zero shape-channel readers (grep receipt: no corner-shape or factor consumer in ui/progress/). Number unit: none.
radius 'small' | 'medium' | 'large' | 'auto' | number'auto'OWN SEAM, AXIS UNREAD — the bar's corner rides the promotion seam var(--progress-radius, 4px) (measured 4px ambient; stamped 9px → 9px live in the probe — a consumer var can re-shape it), and the axis carriers are unread (zero radius-effective readers, grep receipt). Number unit: px.
color 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto'SUPPLY-ONLY — the fill paints var(--primary) straight from the legacy chain (the global brand-hue runtime moves it), with zero --jx-color-effective readers (grep receipt): the hue AXIS never reaches the bar. Number unit: hue degrees.
theme 'light' | 'dark' | 'system' | 'auto''auto'THE FROZEN-INK SEAM, THIRD INSTANCE (measured per element under .dark) — the FILL re-derives: the pseudo rule paints var(--primary), a live legacy chain, so the fill flips to the drifted dark primary (measured oklch(0.6489 …) → 0.7044-family). Everything else freezes: the track (--jx-muted atom), the 1px frame (--jx-border atom) and the label/value readout inks (--jx-muted-foreground/--jx-foreground atoms) all keep their LIGHT values under dark — a brand stripe on a light track with light ink. system/auto = tree inheritance. No number lane.
elevation 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto'SUPPLY-ONLY — zero elevation-carrier readers (grep receipt); the bar carries a 1px frame, no shadow tier. Number unit: dp.
motion 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto'TWO LIVE CLOCKS, BOTH OFF THE AXIS — the indeterminate sweep rides the promotion seams var(--motion-indeterminate, 900ms) linear infinite (measured 0.9s/linear/24px tile) and slows to 4s under prefers-reduced-motion (measured); the determinate fill's authored 200ms decelerate is LIVE — burst-measured (3 runs, clip frames decoded in-page): the edge moves by 32–53ms and settles by 82–99ms, and the tween PERSISTS under reduced-motion (moved 32–36ms, settled 98–103ms — the engine's smoothing is css-unreachable); a 3s-linear injection stretches the paint to ≈4–4.5s across instruments (3.0s by slope fit; 4.1s task-39; 4.5s on the re-verify's CDP-timestamped capture — the stretch is slope-consistent linear, the exact span is instrument-bound), so the transition channel governs; --jx-motion-effective has zero readers (grep receipt). Number unit: coefficient.

Receipts: the two live clocks (sweep 0.9s/linear/infinite with a 24px tile, 4s under reduced motion; the fill edge burst-measured moving by 32–53ms and settling by 82–99ms in normal mode — and PERSISTING under reduced motion, 32–36ms moved / 98–103ms settled across 3 runs, with a 3s-linear injection stretching the paint to a slope-consistent linear crawl (≈4–4.5s, instrument-bound); a single capture taken ≥200ms after the set would read "inert" — burst is the truth instrument for paint transitions), the density-invariant height (10px at the 2xs stamp), the radius seam (4px ambient, 9px stamped), the theme split (fill 0.6489-family → 0.7044-family under .dark while the label ink stayed oklch(0.3211 0 0)) and the platform mapping (indeterminate position −1, valuenow omitted; aria-valuenow=100 at max=250) were measured on this page's served DOM (probe, task 34; the fill-clock burst receipts re-measured at task 39); the unread rows carry grep receipts over ui/progress/. The query() seat below rides the md viewport key (48rem) on the size lane.

one real query() case
<script lang="ts">
  import Progress from '@ui/progress.svelte';
  import { query } from '@lib/universal-props-query.svelte';
</script>

<!-- below 48rem the base (13px) applies; at 48rem+ the md case (18px)
     wins — the readout root scales, the bar stays 10px -->
<Progress value={0.6} label="sync" size={query({ md: 18 }, 13)} />

progress · query()

sync 60%
60%

Media keys are min-width: below 48rem the base (13px root) applies; at 48rem and wider the md case wins (18px) — the readout root scales while the bar stays 10px. The number lane goes bare. Resize across 48rem.

progress-query-demo.svelte svelte
<script lang="ts">
  import Progress from '@ui/progress.svelte';
  import { query } from '@lib/universal-props-query.svelte';
</script>

<!-- below 48rem the base (13px) applies; at 48rem+ the md case (18px)
     wins — the readout root scales, the bar stays 10px -->
<Progress value={0.6} label="sync" size={query({ md: 18 }, 13)} />

Progress · universal props

axes 70%
70%
named steps
progress-universal.svelte svelte
<Progress size={18} density="small">…</Progress>

a11y

Accessibility

The native element already exposes value semantics (indeterminate omits the current value); the component adds only a polite % readout and a generic-name fallback worth knowing about.

Keyboard

KeyAction
—Not a focus stop and no keys of its own — value changes announce through the polite status readout, never by stealing focus

ARIA

AttributeValueDescription
progressbar (implicit)native elementrole, value mapping and the indeterminate state are the platform’s — measured: position 0.42 determinate, position −1 with no valuenow when omitted
aria-valuenow / min / maxnativeExposed by the element (measured aria-valuenow=100 at max=250); no ARIA attributes are authored
aria-labellabel ?? "progress"Names the bar; without a visible label the fallback is the GENERIC "progress" — pass a label when several bars share a page (anchoring note)
role: statuson the % readoutPolite live region — announced when the reader is idle; the value is announced through the relation, not read off the bar (measured "42%")
prefers-reduced-motionsplit, measuredThe stripe sweep slows to 4s (measured); the fill tween is NOT fully suppressible — the css transition is killed outright yet the visible tween persists (engine-side smoothing, css-unreachable; burst-measured across 3 runs)
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h