Skip to main content

Linear Progress

Horizontal progress bar for determinate, indeterminate, and buffer loading states.

Quick start
import { LinearProgress } from "mates-ui";

// Determinate
LinearProgress({ value: 60 });

// Indeterminate
LinearProgress({});

// Buffer
LinearProgress({ value: 40, buffer: 70 });

Determinate

import { html, renderApp } from 'mates';
import { LinearProgress, Text } from 'mates-ui';

const App = () => () => html`
<x-col gap="var(--md-space-4)" style="width:100%;max-width:32rem;">
<x-col gap="var(--md-space-1)">
${Text("25%", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 25, label: "25% complete" })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("50%", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 50, label: "50% complete" })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("75%", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 75, label: "75% complete" })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("100%", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 100, label: "100% complete" })}
</x-col>
</x-col>
`;

renderApp(App, document.getElementById('app'));

Indeterminate

import { html, renderApp } from 'mates';
import { LinearProgress } from 'mates-ui';

const App = () => () => html`
<div style="width:100%;max-width:32rem;">
${LinearProgress({ label: "Loading" })}
</div>
`;

renderApp(App, document.getElementById('app'));

Buffer

import { html, renderApp } from 'mates';
import { LinearProgress } from 'mates-ui';

const App = () => () => html`
<div style="width:100%;max-width:32rem;">
${LinearProgress({ value: 40, buffer: 70, label: "40% played, 70% buffered" })}
</div>
`;

renderApp(App, document.getElementById('app'));

Sizes

import { html, renderApp } from 'mates';
import { LinearProgress, Text } from 'mates-ui';

const App = () => () => html`
<x-col gap="var(--md-space-4)" style="width:100%;max-width:32rem;">
<x-col gap="var(--md-space-1)">
${Text("sm", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 60, size: "sm", label: "Small" })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("md (default)", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 60, size: "md", label: "Medium" })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("lg", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 60, size: "lg", label: "Large" })}
</x-col>
</x-col>
`;

renderApp(App, document.getElementById('app'));

Interactive

import { html, atom, renderApp } from 'mates';
import { LinearProgress, Text, OutlinedButton } from 'mates-ui';

const App = () => {
const progress = atom(40);
const dec = () => progress.set((v) => Math.max(0, v - 10));
const inc = () => progress.set((v) => Math.min(100, v + 10));
return () => html`
<x-col gap="var(--md-space-4)" style="width:100%;max-width:32rem;">
${LinearProgress({ value: progress(), label: `${progress()}% complete` })}
<x-row gap="var(--md-space-3)" align="center">
${OutlinedButton("−10", { size: "sm", disabled: progress() <= 0, onClick: dec })}
${Text(`${progress()}%`, { type: "label-md", style: { minWidth: "3rem", textAlign: "center" } })}
${OutlinedButton("+10", { size: "sm", disabled: progress() >= 100, onClick: inc })}
</x-row>
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Color override

import { html, renderApp } from 'mates';
import { LinearProgress, Text } from 'mates-ui';

const App = () => () => html`
<x-col gap="var(--md-space-4)" style="width:100%;max-width:32rem;">
<x-col gap="var(--md-space-1)">
${Text("Rose", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 65, label: "Rose", style: { "--md-lp-fill": "#f43f5e", "--md-lp-track": "#ffe4e6" } })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("Amber", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 45, label: "Amber", style: { "--md-lp-fill": "#f59e0b", "--md-lp-track": "#fef3c7" } })}
</x-col>
<x-col gap="var(--md-space-1)">
${Text("Emerald + buffer", { type: "label-sm", color: "muted" })}
${LinearProgress({ value: 30, buffer: 60, label: "Emerald", style: { "--md-lp-fill": "#10b981", "--md-lp-buffer": "#a7f3d0", "--md-lp-track": "#d1fae5" } })}
</x-col>
</x-col>
`;

renderApp(App, document.getElementById('app'));

LinearProgress

import { LinearProgress } from "mates-ui";
Prop Type Default Description
attr AttrMap Extra HTML attributes forwarded to progress parts: `root`, `track`, `bar`, `buffer`.
buffer number 0–100 secondary (ghost) track. Requires value to be set — shows buffer variant.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string "Loading" aria-label for the progressbar role.
on OnEventMap DOM event handlers on the root element.
size "sm" | "md" | "lg" "md" Track height.
style StyleMap Shorthand for `style`. Inline styles applied to the root element.
style StyleMap Inline CSS on the root element.
testId string "md-linear-progress" Overrides the default `data-testid` attribute on the root element.
value number 0–100. When omitted, renders as indeterminate.

CSS tokens

Token Role
--md-lp-buffer Buffer (ghost) track color
--md-lp-fill Filled bar color
--md-lp-track Track (background) color

Caveats

  • Omit `value` entirely to trigger the indeterminate animation — passing `value={undefined}` also works.
  • `buffer` has no visual effect when `value` is omitted; indeterminate mode ignores both props.
  • CSS variable overrides must be passed via `style` — inline style strings are not forwarded.