Progress & loading
Show that work is in progress, or load more.
Spinner #
Loading indicator.
<Spinner label="Loading deploys" />| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | "Loading" | Accessible name announced to screen readers. | |
size | "sm" | "md" | "lg" | "md" | Spinner size. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Progress #
Bars, steps, rings and semicircles.
Progress #
Linear progress bar.
<Progress value={64} color="success" />Slots: root bar
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value | number | yes | Current progress, from 0 to max. | |
color | "warning" | "success" | "info" | "danger" | "primary" | "primary" | Bar colour. | |
indeterminate | boolean | Unknown duration: an animated bar instead of value. | ||
max | number | 100 | Value at 100%. | |
size | "sm" | "md" | "lg" | "md" | Bar thickness. |
ProgressColor
type ProgressColor = "primary" | "info" | "success" | "warning" | "danger" | undefined;ProgressSize
type ProgressSize = "sm" | "md" | "lg" | undefined;SteppedProgress #
Progress split into discrete steps.
<SteppedProgress label="Onboarding" steps={4} value={2} />Slots: root segment
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
steps | number | yes | Number of segments. | |
value | number | yes | Completed segments. | |
label | string | "Progress" | Accessible name (default "Progress"). |
RingProgress #
Circular progress.
<RingProgress value={72} label="72%" />Slots: root bar label track
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value | number | yes | Progress in percent (0–100). | |
label | content | Content in the middle, e.g. 72%. | ||
size | number | 72 | Diameter in pixels. | |
thickness | number | 6 | Ring stroke width in pixels. |
SemiCircleProgress #
Half-ring gauge.
<SemiCircleProgress value={64} label="64%" />Slots: root bar label track
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value | number | yes | Progress in percent (0–100). | |
label | content | Content in the middle, e.g. 64%. | ||
size | number | 120 | Width in pixels. | |
thickness | number | 10 | Arc stroke width in pixels. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
NavigationProgress #
Top loading bar (Mantine NavigationProgress / NProgress).
40%
function Example() {
const progress = signal(40);
const running = signal(true);
// Trickle towards 90% while "navigating" (effects run only in the browser).
effect(() => {
if (!running()) return;
const timer = setInterval(
() => progress.set(Math.min(90, progress() + (90 - progress()) * 0.2)),
300,
);
return () => clearInterval(timer);
});
return (
<Stack gap="0.75rem" style={{ "padding-top": "0.75rem" }}>
{/* Pinned to the top of the page (here: of the preview). */}
<NavigationProgress visible={progress() < 100} value={progress()} />
<Group gap="0.5rem">
<Button
size="sm"
onClick={() => {
progress.set(5);
running.set(true);
}}
>
Start navigation
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
running.set(false);
progress.set(100);
}}
>
Finish
</Button>
<Text muted>{progress() >= 100 ? "Done" : `${Math.round(progress())}%`}</Text>
</Group>
</Stack>
);
}Slots: root bar
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
visible | boolean | yes | Show the bar (hide it when navigation finishes). | |
label | string | "Loading" | Accessible name (default "Loading"). | |
value | number | 30 | 0–100; omit for indeterminate. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
LoadingOverlay #
Covers its positioned parent with a spinner while visible.
Deploy list
function Example() {
const loading = signal(true);
const refresh = () => loading.set(true);
// Pretend each refresh takes 1.5 s (effects run only in the browser).
effect(() => {
if (!loading()) return;
const timer = setTimeout(() => loading.set(false), 1500);
return () => clearTimeout(timer);
});
return (
<Stack gap="0.5rem">
<Box style={{ position: "relative", "min-height": "6rem" }}>
<Text>Deploy list</Text>
<LoadingOverlay visible={loading()} label="Refreshing" />
</Box>
<Button size="sm" variant="outline" onClick={refresh} disabled={loading()}>
Refresh
</Button>
</Stack>
);
}Slots: root spinner
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
visible | boolean | yes | Show the overlay and spinner. | |
label | string | "Loading" | Accessible name of the spinner. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Skeleton #
Placeholders while content loads.
Skeleton #
Loading placeholder sized by width / height.
<Skeleton width="16rem" height="1.25rem" />| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
height | string | "0.9rem" | Height (any CSS length). | |
width | string | "100%" | Width (any CSS length). |
SkeletonText #
Placeholder text lines.
<SkeletonText lines={3} />Slots: root line
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
lines | number | 3 | Number of placeholder lines. |
SkeletonCard #
Placeholder card.
<SkeletonCard />Slots: root block text
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Load more #
Incremental loading of lists.
LoadMore #
"Load more" button that shows loading and end-of-list states.
Activity #1
Activity #2
Activity #3
function Example() {
const count = signal(3);
const loading = signal(false);
const load = () => {
loading.set(true);
setTimeout(() => {
count.set(count() + 3);
loading.set(false);
}, 500);
};
return (
<Stack gap="0.5rem">
<For each={Array.from({ length: count() }, (_, i) => i + 1)}>
{(n) => <Text>Activity #{n}</Text>}
</For>
<LoadMore
onLoad={load}
loading={loading()}
hasMore={count() < 12}
endLabel="That's everything."
>
Load 3 more
</LoadMore>
</Stack>
);
}Slots: root button end
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onLoad | () => void | yes | Called when the user asks for more. | |
children | content | "You're all caught up" | Button label. | |
endLabel | content | Text when everything is loaded (default "You're all caught up"). | ||
hasMore | boolean | Whether more items can load; shows endLabel when false. | ||
loading | boolean | Show a spinner and disable the button. |
InfiniteScroll #
Content followed by a "Load more" control.
Deploy #128
Deploy #127
Deploy #126
Deploy #125
Deploy #124
Deploy #123
function Example() {
const deploys = signal(Array.from({ length: 6 }, (_, i) => 128 - i));
const loading = signal(false);
const hasMore = () => deploys().length < 30;
const loadMore = () => {
if (loading() || !hasMore()) return;
loading.set(true);
setTimeout(() => {
const last = deploys()[deploys().length - 1] ?? 128;
deploys.set([...deploys(), ...Array.from({ length: 6 }, (_, i) => last - 1 - i)]);
loading.set(false);
}, 600);
};
return (
<ScrollArea maxHeight="12rem">
<InfiniteScroll onLoadMore={loadMore} hasMore={hasMore()} loading={loading()}>
<Stack gap="0.5rem">
<For each={deploys()}>{(n) => <Text>Deploy #{n}</Text>}</For>
</Stack>
</InfiniteScroll>
</ScrollArea>
);
}Slots: root sentinel
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onLoadMore | () => void | yes | Called when the end of the list scrolls into view (or the button is pressed). | |
children | content | The list rendered so far. | ||
hasMore | boolean | Whether more items can load. | ||
loading | boolean | Currently loading (prevents duplicate requests, shows a spinner). | ||
loadMoreLabel | content | "Load more" | Button text (default "Load more" / "Loading…"). | |
rootMargin | string | "200px" | How far before the end to start loading (IntersectionObserver rootMargin). Default 200px. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.