Loader
PortedSpinner that tells screen readers what is loading (role="status"). Its own SVG, not an icon: a faint track and an arc that grows and turns, in CSS only. With delay, the status is there from the start but empty and without a name; the spinner, its text and its name come in after the delay, so they are announced then.
import { Loader } from "@etched/monet/loader";
export function Preview() {
return <Loader aria-label="Loading runs" />;
}Installation
import { Loader, Skeleton, SkeletonLine } from "@etched/monet/loader";Examples
Sizes
Three presets, or a size in px where they do not fit.
import { Loader, type LoaderSize } from "@etched/monet/loader";
const sizes: LoaderSize[] = ["sm", "base", "lg"];
export function Sizes() {
return (
<div className="row">
{sizes.map((s) => (
<Loader key={s} size={s} />
))}
<Loader size={48} aria-label="Loading, 48 px" />
</div>
);
}Delay
delay waits before the spinner shows and is announced, so a load that ends quickly does not flash it. Its room is kept, so nothing moves.
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { Loader } from "@etched/monet/loader";
export function Delay() {
const [runs, setRuns] = useState(0);
return (
<div className="row">
<Button onClick={() => setRuns(runs + 1)}>Load again</Button>
<Loader key={runs} delay={400} aria-label="Loading after a delay" />
</div>
);
}Inline, with its own label
Chip SH-24A-0193 Loading chip status is being checked.
import { Loader } from "@etched/monet/loader";
export function InlineWithItsOwnLabel() {
return (
<p>
Chip SH-24A-0193 <Loader size="sm" aria-label="Loading chip status" /> is being checked.
</p>
);
}Page
page fills the space it is given and centers the spinner, for example as a Suspense fallback.
import { Loader } from "@etched/monet/loader";
const frame = { display: "flex", blockSize: "14rem", borderRadius: "var(--et-radius-lg)", boxShadow: "inset 0 0 0 1px var(--et-line)" };
export function Page() {
return (
<div style={frame}>
<Loader page aria-label="Loading referrals" />
</div>
);
}Skeleton lines
A line is as tall as a line of the text around it, so the layout does not move when the text arrives.
import { SkeletonLine } from "@etched/monet/loader";
const ring = { borderRadius: "var(--et-radius-lg)", boxShadow: "inset 0 0 0 1px var(--et-line)", paddingInline: "var(--et-space-3)" };
export function SkeletonLines() {
return (
<div className="grid">
<div>
<div style={{ fontSize: "var(--et-text-heading)", lineHeight: "var(--et-leading-heading)", inlineSize: "40%" }}>
<SkeletonLine />
</div>
<SkeletonLine />
<SkeletonLine />
<SkeletonLine style={{ inlineSize: "60%" }} />
</div>
<div className="row" style={{ flexWrap: "nowrap" }}>
<span>Owner</span>
<SkeletonLine style={{ inlineSize: "8rem" }} />
</div>
<div style={ring}>
<SkeletonLine style={{ blockSize: "var(--et-control-base)" }} />
</div>
</div>
);
}Skeleton shapes
Skeleton stands in for a block (a card or a chart), a circle (an avatar) or a paragraph (lines, the last one shorter).
import { Skeleton, SkeletonLine } from "@etched/monet/loader";
export function SkeletonShapes() {
return (
<div className="grid" aria-busy="true">
<div className="row" style={{ flexWrap: "nowrap", alignItems: "start", gap: "var(--et-space-3)" }}>
<Skeleton shape="circle" />
<div style={{ flex: 1 }}>
<SkeletonLine style={{ inlineSize: "40%" }} />
<Skeleton shape="text" lines={2} />
</div>
</div>
<Skeleton style={{ blockSize: "8rem" }} />
<Skeleton shape="text" />
</div>
);
}Accessibility
Loaderis astatusnamed byaria-label(default "Loading"). The spinner is hidden; visually hidden text holds the same words.- With
delay, the status is there but empty and unnamed. The spinner and its text come in after the delay, so screen readers hear them then. SkeletonandSkeletonLinehavearia-hidden, so screen readers do not hear them.- Pass a translated label that says what loads, such as "Loading runs". With skeletons, mark the loading region
aria-busyor add aLoader.
API reference
Loader
| Prop | Type | Default | Description |
|---|---|---|---|
| size | LoaderSize | number | "base", or "lg" with `page` | A preset, or a size in px for a place the presets do not fit. |
| aria-label | string | "Loading" | Names the status, and screen readers announce it. Pass translated text. |
| page | boolean | – | Fill the space it is given and center the spinner. For a page or panel that loads, for example a Suspense fallback. |
| delay | number | 0 | Milliseconds to wait before the spinner shows and is announced, so a fast load does not flash it. 300 to 500 suits most loads. |
Skeleton
| Prop | Type | Default | Description |
|---|---|---|---|
| shape | "block" | "circle" | "text" | "block" | |
| lines | number | 3 | How many lines shape="text" shows. |