Skip to content

ExpressiveCSS

Browse documentation

Loading overlay

Covers part of a page while it loads, so nothing inside takes input until the page clears it.

View as Markdown

Use .loading-overlay to cover part of a page while it loads, so nobody edits or presses what is about to change.

Put a loading indicator in the region as a direct child, then the content. AutoInit() starts every .loading-overlay. While the region has aria-busy="true", a scrim covers it, the indicator shows in its centre, and nothing inside takes a click, a tap or keyboard focus. Press Save below to see it.

<form class="loading-overlay">
  <span class="loading-indicator contained" role="status" aria-label="Saving"></span>
  <div class="field">
    <input id="name" type="text" placeholder=" ">
    <label for="name">Name</label>
  </div>
  <button type="submit" class="filled">Save</button>
</form>

Marking it busy

The page sets aria-busy="true" on the region when the work starts, and removes it, or sets any other value, when the work is done. There is no method for either. The script watches the attribute and makes the region inert while it is busy. When the region clears, it gets back the inert state it had before.

const region = document.querySelector('.loading-overlay');
region.setAttribute('aria-busy', 'true');
try {
  await save();
} finally {
  region.removeAttribute('aria-busy');
}

Inert content leaves the accessibility tree, the indicator's own status included. So the script adds a visually hidden status region just after the region, which announces "Loading" when the region becomes busy and "Loaded" when it clears. Set the words with the i18n option.

The script takes focus from a control inside the region when it becomes busy, including one in a shadow root. When the region clears, focus goes back to that control, unless the person moved focus somewhere else in the meantime or the page removed the control.

The scrim and the indicator show from the attribute alone, before the script starts and after destroy(), but only the script blocks input. They stack above anything a component raises inside itself, at z-index 5 and 6; content the page raises higher shows through. They fade in and out, and appear at once under reduced motion. In forced colors the scrim dims the content toward the system background, so it still looks unavailable.

Regions that scroll

The scrim scrolls with the content of a region that scrolls itself, so it would only cover the first screenful. Put the overlay on a wrapper around the scroller instead. The region becomes a positioning context for the scrim, with position: relative, unless a utility such as .sticky-top positions it already.

<div class="loading-overlay">
  <span class="loading-indicator contained" role="status" aria-label="Loading"></span>
  <div style="height: 24rem; overflow-y: auto">...rows</div>
</div>

Options

Name Type Default Description
i18n Object { loading: 'Loading', loaded: 'Loaded' } What the status region announces when the region becomes busy and when it clears.

Methods

const overlay = Expressive.LoadingOverlay.getInstance(document.querySelector('.loading-overlay'));
overlay.destroy(); // stops following aria-busy, removes the status region, gives inert back;
                   // the scrim still follows the attribute

Tokens

Override these on the .loading-overlay.

Token Default
--md-comp-loading-overlay-scrim-colorsurface at 70%
--md-comp-loading-overlay-motiondefault effects spring
--md-comp-loading-overlay-motion-durationthe default effects spring's duration

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow