Loading states
Tell people something is happening, and keep the page from jumping when it finishes.
Which indicator?
Section titled “Which indicator?”| Component | Use it when | Example |
|---|---|---|
holo-skeleton |
You know the shape of the content that is loading | A card, list row or avatar |
holo-spinner |
You do not know the shape, and the wait is short | A button action, a small refresh |
holo-progress |
The wait is long, or you can measure it | An upload, a multi-step import |
Rules of thumb:
- Under about a second, show nothing, or only disable the control. A flash of loading UI is worse than a short wait.
- Prefer a skeleton for page and panel content. It previews the layout and keeps it stable.
- Use a spinner for a wait of unknown length that has no content shape to mimic.
- Use progress when you can say how far along you are.
Spinner
Section titled “Spinner”holo-spinner has role="status" and a visually hidden label (default “Loading…”) that assistive technology announces. Set a label that says what is loading.
<holo-spinner label="Saving changes"></holo-spinner>Skeleton
Section titled “Skeleton”holo-skeleton is a placeholder block, hidden from assistive technology (aria-hidden="true"). Its variant is text, circle or rect, and width and height take any CSS length. Match the size of the real content.
<div aria-busy="true"> <holo-skeleton variant="circle" width="3rem" height="3rem"></holo-skeleton> <holo-skeleton variant="text" width="60%"></holo-skeleton> <holo-skeleton variant="text" width="90%"></holo-skeleton></div>Progress
Section titled “Progress”holo-progress has role="progressbar" and uses label as its accessible name.
- Determinate: set
value(andmax, default 100). Use it when you can measure progress. - Indeterminate: set
indeterminate. Use it for work of unknown length. It omitsaria-valuenow, so screen readers report it as busy without a number.
<holo-progress label="Uploading report.pdf" value="40"></holo-progress> <holo-progress label="Preparing export" indeterminate></holo-progress>Update value as the work advances. Switch to determinate as soon as you have a real figure.
Mark busy regions with aria-busy
Section titled “Mark busy regions with aria-busy”Set aria-busy="true" on the container that is being updated, and remove it when the content is ready. Assistive technology then waits for the finished content instead of announcing each partial change.
panel.setAttribute('aria-busy', 'true');panel.replaceChildren(...skeletons);
const data = await load();panel.replaceChildren(render(data));panel.removeAttribute('aria-busy');aria-busy does not announce anything by itself. Pair it with a holo-spinner or a status message when the user triggered the wait, so they hear that work started.
Avoid layout shift
Section titled “Avoid layout shift”- Give skeletons the same dimensions as the content that replaces them, so nothing moves on load.
- Reserve space for content that arrives later, with
min-heightoraspect-ratio, instead of letting the page grow. - Swap content in place. Do not insert a loading banner above existing content.
- Keep a button the same width while it loads: show the spinner beside the label or inside a fixed-width button, rather than replacing the label.
- Respect
prefers-reduced-motion. The Hololink UI loaders do, so do not add your own looping animations.