JavaScript Progress

@lemonadejs/progress · ✓ 17 contract checks · framework-agnostic · zero dependencies

One block, both progress shapes: linear (track + bar) and circular (SVG stroke arc), selected by type. Determinate when a percent is bound, indeterminate otherwise (or when forced):

<${Progress} bind=”${pct}” label /> linear, determinate <${Progress} type=“circular” bind=”${pct}” /> circular, determinate <${Progress} /> linear, indeterminate <${Progress} type=“circular” indeterminate /> spinner

bind vs indeterminate (by design): bind=”${state}” the live percent 0-100 (clamped); ABSENT → indeterminate indeterminate forces the looping animation even with a value

Geometry is deterministic and testable: the linear bar carries an inline width:%, the circular arc carries stroke-dasharray/stroke-dashoffset computed from percent and the radius (size - thickness) / 2. The looping animations are pure CSS keyframes driven by data-indeterminate.

Example

live
import { html } from 'lemonadejs';
import Progress from '@lemonadejs/progress';

const App = (props, { state, onUnmount }) => {
    const percent = state(0);
    // A fake upload: one bound percent drives every determinate bar below
    const timer = setInterval(() => (percent.value = percent.value >= 100 ? 0 : percent.value + 1), 60);
    onUnmount(() => clearInterval(timer));

    return html`<div style="display:grid;gap:18px;max-width:420px">
        <${Progress} bind="${percent}" label />
        <${Progress} bind="${percent}" thickness="8" color="green" />
        <div style="display:flex;gap:24px;align-items:center">
            <${Progress} type="circular" bind="${percent}" label size="64" thickness="5" color="orange" />
            <${Progress} type="circular" indeterminate />
        </div>
        <${Progress} indeterminate color="purple" />
    </div>`;
};

Installation

npm install @lemonadejs/progress
import Progress from '@lemonadejs/progress';
import '@lemonadejs/progress/style.css';

Three deployment forms, one component:

html`<${Progress} />`                       // by value (no registration)
setComponents({ Progress });               // then <Progress /> by name anywhere
createWebComponent(Progress);              // <lm-progress> in plain HTML/any framework

Props

Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.

PropTypeDefaultDescription
bindnumberTwo-way bound value. .set() fires onchange; plain assignment is silent. two-way percent 0-100; absent → indeterminate
typestring''” = linear
indeterminatebooleanfalseforce the looping animation even with a value
sizenumber0circular diameter in px (default 40 via CSS)
thicknessnumber0stroke/bar thickness in px (defaults: 4 linear, 3.6 circular)
colorstring''green
labelbooleanfalseshow the % text: beside linear, centered in circular
arialabelstring''accessible name for the progressbar (aria-label)

Events

All event names are lowercase (the platform convention — LJS-305 warns otherwise).

  • onchange — fires when the bound percent is set via set()

Styling

All classes follow the lm-progress-* convention; visual variants are data-* attributes on the root. Override freely — there is no styling engine to fight.

Contract

The machine-readable schema ships with the package:

import contract from '@lemonadejs/progress/contract.json';

verify.json carries the conformance proof produced by verify(Progress).

Looking for the v5 plugin? See the archived v5 documentation.