Circular Progress
A compact task indicator with the same measured/unknown semantic contract as linear progress.
Choose the compact form
Use circular progress in a compact task region with a nearby task name, such as a pending action or small status area. Prefer a linear track when its range and progression benefit from a horizontal reading path. Do not replace a persistent failure message with an endless spinner.
This is an exported MobTranslate web component using the same Base UI progress root, not a claim to reproduce every Material Expressive animation or native platform behavior.
Anatomy and sizes
| Part | Specification |
|---|---|
| sm / md / lg | 20 / 32 / 48 CSS px; md default |
| SVG | 24×24 viewBox; r=10; stroke width=3 in viewBox units |
| Track / indicator | --color-muted / --color-primary; SVG is decorative |
| Measured arc | Percentage of the finite min..max range |
| Unknown arc | Static quarter arc rotating as a whole SVG; no asserted percentage |
| Reduced motion | Static quarter arc, readable task text retained |
| Hit target | Read-only indicator dimensions are not action-target dimensions |
Short loading and refresh adaptation
Material’s expressive loading indicator is distinct from its circular progress indicator: a seven-shape loop, optionally contained, for short unknown waits. Its reference dimensions include a 48dp overall region/38dp shape area and 24–240dp scaling. Those shapes, dimensions, container color mapping and Android pull-to-refresh are not exported by this web package. Here, a named unknown CircularProgress plus persistent task text provides the web loading pattern; its 20/32/48 CSS px sizes and static reduced-motion fallback are specified above.
Use the uncontained arc on a clear task surface. If content behind it is busy, give the entire status region an opaque contrasting surface and preserve content clearance. Check the active arc against that surface; putting a spinner over a photograph does not establish contrast. The reference uses primary on a plain surface and on-primary-container within its contained variant; those are reference role names, not additional local color props.
- Refreshing must have an explicit named button or menu command usable without a swipe. The existing Search pattern demonstrates an actual request, cancellation and retained prior results; its request state, rather than a gesture animation, owns the outcome.
- For a custom pull gesture, require an intentional threshold, let reversing the gesture cancel before commitment, and retain status until content is ready or the task is left. This guide does not provide a browser gesture recognizer or claim Android system refresh integration.
- Keep unknown progress without aria-valuenow, name the affected content and use visible status for error, cancellation and recovery. Loading motion cannot be the only information.
- The side-by-side circular and linear example below compares two visualizations of the same manual local steps; it is not advice to duplicate indicators for one production process. No wall-clock animation advances those measured steps.
Working circular and linear comparison
No timer invents progress. Choose measured local steps or an unknown total explicitly.
Local task underway; total unknown.
'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
CircularProgress exports CircularProgressProps: the Progress root contract plus size=sm|md|lg (md default). Its ref is HTMLDivElement. The SVG and its circles are wrapper-owned; provide labels through ARIA or adjacent text, not child content.
| Prop | Type / default | Contract |
|---|---|---|
| value | number | null for Progress; number for Meter | Supply an explicit value; null means an unknown task total, not zero. |
| min / max | number; 0 / 100 | Finite range with min < max; validate application measurements before rendering. |
| aria-label / aria-labelledby | Native ARIA props | Name the task or measurement; the colored track is not a label. |
| getAriaValueText | Callback(formattedValue, value) → string | Describe meaningful units. Progress callback arguments can be null. |
| aria-valuetext | string | Explicit alternative when application copy supplies the value. |
| locale / format | Intl locale and NumberFormat options | Formatting does not translate content or establish its provenance. |
| className | string or root state callback | Merged with package classes; Progress state has status. |
| style / render / native props / ref | Installed Base UI root contract; HTMLDivElement ref | Custom rendering must preserve forwarded semantics and ref. |
| children | Wrapper-owned structure | These wrappers render their own track/indicator; place labels and outcomes adjacent. |
State and accessibility contract
- Measured value, min, max and aria-valuetext must describe the same task as the visible arc.
- For value=null, expose unknown progress without aria-valuenow. The quarter arc does not mean 25% completion.
- Name the root; hide the decorative SVG to avoid a duplicate graphic announcement.
- Progress state callbacks receive indeterminate, progressing or complete status. Custom class callbacks retain the package’s classes.
- Keep a meaningful enabled focus destination in the surrounding task if a pending control changes.
Failure cases
- An unnamed animated glyph as the only task status.
- An unknown quarter arc announced as 25%.
- The small visual circle mistaken for an adequate touch button.
- An action’s label disappearing beneath a spinner, or continuous motion despite reduced-motion preference.
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.