MobTranslate styleguide

Progress

Show task completion with a linear indicator; unknown totals remain unknown.

Task progress versus measurement

Use Progress while a task is underway. Use value=null when the total or fraction is not measured. Zero means a known task has completed none of its range; it is different from an unknown total. Completion at max is a measurement, not automatic proof that a save or publication succeeded.

Use Meter for a bounded scalar such as current storage use, Skeleton for a content placeholder, and CircularProgress when a compact indicator fits the task. Keep the task name and outcome in text.

Anatomy and states

State / partSpecificationMeaning
Track100% container width; 0.5rem height (8px at a 16px root); --color-muted and --radius-fullAvailable task range.
Measured range endpointDecorative 4 CSS px circle, 2px from the logical end; --color-primaryDistinguishes the full range from a low-contrast muted track. Absent for unknown totals; merges into a full active bar at completion.
Determinate indicatorPercentage of min..max; --color-primaryMeasured completion only.
Indeterminate indicator35% moving segment; no aria-valuenowTotal unknown, not a full bar.
Completevalue=max; data-completePair with an actual task outcome.
Reduced motionStatic partial segment, width transition removedTask stays understandable without motion.
Forced colorsCanvasText track border and Highlight indicatorInspect the active OS/browser theme.

Reference timing, scope and placement

Material recommends no loading indicator for expected waits under 200ms, its expressive loading indicator for roughly 200ms–5s, and progress indicators for longer waits. These are product latency guidelines, not a reason to delay ready content or fabricate a duration. If a task may acquire a measured total, use an indeterminate Progress and transition to its measured state; the reference discourages transitioning a shape-morph loading indicator into measured progress.

Choose one indicator for one aggregate process; independently initiated tasks may have independent status. Put linear progress along the affected container edge or within its stable status region, and compact circular progress in the affected region. When appending content, use the space reserved for the new content rather than covering readable records. Keep the same form for the same process across the product.

Reference choiceLocal web decision
Anatomy and contrastActive portion, track and measured end marker. The marker exposes the endpoint because the muted track alone is below 3:1 against the default page surfaces. Indicator and marker require at least 3:1 against their actual adjacent surface; custom themes must be checked again.
Flat versus expressiveThis library has a flat linear bar and a circular arc; no wavy amplitude/wavelength or shape-morph configuration API is implemented. The local track is 0.5rem, not a conversion of the reference default 4dp.
DirectionLinear progression and its endpoint follow logical inline direction. Explicit LTR task islands retain LTR; circular motion remains clockwise.
Size and placementLinear width follows its container. Reference 4dp edge insets, 40dp minimum container and 24–240dp circular range are native design guidance, not this package’s CSS sizing contract. Check real content space and local size specifications.
Buttons and long workA spinner inside an action must contrast with that action and preserve its name. Current CircularProgress has a visible track; use a composed style checked against the action, not a claim of a trackless preset. Long tasks need explicit cancellation or a safe way to leave and find their eventual outcome.

Working measured and unknown states

No timer invents progress. Choose measured local steps or an unknown total explicitly.

Local task underway; total unknown.

tsx
'use client';
import { useState } from 'react';
import { Button, CircularProgress, Meter, Progress } from '@mobtranslate/ui';

export function ProgressCommunicationExample() {
  const [value, setValue] = useState<number | null>(null);
  const [size, setSize] = useState<'sm' | 'md' | 'lg'>('md');
  const text = value === null ? 'Local task underway; total unknown.' : `${value} of 10 local steps complete.`;
  return <div data-progress-lab className="space-y-4">
    <p>No timer invents progress. Choose measured local steps or an unknown total explicitly.</p>
    <label>Circular size <select value={size} onChange={event => setSize(event.target.value as typeof size)} className="mt-input"><option value="sm">sm · 20px</option><option value="md">md · 32px</option><option value="lg">lg · 48px</option></select></label>
    <Progress className={state => `lab-progress-${state.status}`} value={value} min={0} max={10} aria-label="Local task progress" getAriaValueText={() => text} />
    <CircularProgress className={state => `lab-circular-${state.status}`} size={size} value={value} min={0} max={10} aria-label="Circular local task progress" getAriaValueText={() => text} />
    <div className="flex flex-wrap gap-3">
      <Button type="button" variant="secondary" onClick={() => setValue(current => Math.min(10, (current ?? 0) + 1))}>Complete one local step</Button>
      <Button type="button" variant="outline" onClick={() => setValue(0)}>Reset measured progress</Button>
      <Button type="button" variant="ghost" onClick={() => setValue(null)}>Use unknown total</Button>
    </div>
    <p role="status" aria-atomic="true">{text}</p>
  </div>;
}

export function MeterCommunicationExample() {
  const [value, setValue] = useState(4);
  return <div data-meter-lab className="space-y-4">
    <label className="block">Local storage sample <input type="range" min="0" max="20" value={value} onChange={event => setValue(Number(event.target.value))} className="block w-full" /></label>
    <Meter value={value} min={0} max={20} aria-label="Local storage sample" getAriaValueText={() => `${value} of 20 sample units used`} />
    <p>{value} of 20 sample units used. This scalar measurement is not task progress, confidence or language proficiency.</p>
  </div>;
}

API reference

PropType / defaultContract
valuenumber | null for Progress; number for MeterSupply an explicit value; null means an unknown task total, not zero.
min / maxnumber; 0 / 100Finite range with min < max; validate application measurements before rendering.
aria-label / aria-labelledbyNative ARIA propsName the task or measurement; the colored track is not a label.
getAriaValueTextCallback(formattedValue, value) → stringDescribe meaningful units. Progress callback arguments can be null.
aria-valuetextstringExplicit alternative when application copy supplies the value.
locale / formatIntl locale and NumberFormat optionsFormatting does not translate content or establish its provenance.
classNamestring or root state callbackMerged with package classes; Progress state has status.
style / render / native props / refInstalled Base UI root contract; HTMLDivElement refCustom rendering must preserve forwarded semantics and ref.
childrenWrapper-owned structureThese wrappers render their own track/indicator; place labels and outcomes adjacent.

Behavior and announcements

  • The root supplies progressbar semantics through the installed primitive. Supply a meaningful name and task-specific value text.
  • Use a stable layout region and preserve input while work is pending. A progress display is not the cancel control.
  • Do not advance a timer to simulate server completion in a real task. Measure real work or report an unknown total.
  • Avoid an announcement for every animation frame. Announce meaningful milestones and the verified outcome.
  • A stalled, failed or cancelled task needs explicit text and recovery; the bar alone cannot describe those states.

Failure cases

  • Unknown work rendered as 100% because the indicator has auto width.
  • A scalar measurement or confidence score presented as task completion.
  • NaN, infinite or reversed bounds supplied as if they were meaningful data.
  • A complete bar before the output-side result is verified, or a retry that discards input.

Verification and release scope

  • Operate the checked example with keyboard and pointer; inspect the actual name, role, relationships and focus destination.
  • Check both themes, narrow reflow, long labels, reduced motion and forced colors. Text contrast and meaningful graphic contrast are separate checks.
  • Test the relevant waiting, completion, failure, cancellation and recovery paths. A local simulation is not a verified backend result.
  • Use a named screen-reader/browser pair to verify spoken output. DOM attributes and automated passes do not prove the announcement.
  • Retain source, review and permission evidence when applying these controls to a real language workflow. Color and motion cannot establish authority.