GitHub

Loading Overlay

Covers its positioned parent with a centered spinner and optional message while a region is busy. Opaque or semi-transparent backdrop.

Stable .astro import EsaLoadingOverlay from '@esa/ecology/esa-loading-overlay.astro';

Preview

Place the overlay inside a position: relative container; its absolute backdrop fills the parent.

Underlying content that is being covered while loading.

<div style="position: relative; min-height: 180px;">
  <p>Underlying content being covered.</p>
  <LoadingOverlay active={true} message="Loading data…" />
</div>

States

A transparent backdrop lets the content show through faintly. When active is false the overlay renders nothing.

You can still see this content faintly through the overlay.

Inactive — the overlay renders nothing.

<LoadingOverlay active={true} transparent={true} message="Saving…" />
<LoadingOverlay active={false} />

API

PropTypeDefaultDescription
active boolean false When false the overlay renders nothing.
message string — Optional caption shown under the spinner.
transparent boolean false Semi-transparent backdrop instead of an opaque one.

Accessibility

  • The backdrop is a single named graphic — role="img" with message as its accessible name, plus aria-busy — and the visual content inside it is aria-hidden so the name is not read twice. It is not a role="status" live region: this page said so until 2026-08-18 and the source had already moved off it, because a region created in the same tick as its text does not announce.
  • The backdrop sits at z-index: 10 over the parent and intercepts pointer interaction while active.
  • It does not stop the keyboard, and it cannot. The overlay is position: absolute, scoped to its nearest positioned ancestor — so a keyboard user can still Tab into controls sitting under an opaque backdrop, and the component cannot make that region inert because it does not own it. Making the covered region inert while loading is the call site's job.
  • For long operations, consider moving focus or announcing the busy state at the call site.

Theming surface

Every public token this component reads, extracted from its source at build time, split by blast radius: the hooks that belong to this component, then the shared and system tokens it consumes alongside everything else. The Resolves to column walks each token's real lineage — component → semantic → primitive → raw value — so you can see exactly where re-pointing it lands. A spoke re-skins by overriding these in its theme-<slug>.css; component internals are never edited. No hook for what you need? File it with /request-lego.

Type roles it names

This component does not set a size, weight, face or tracking of its own — it names a composite, and the composite carries all five. A composite is a class rather than a token, so none of it appears in the tables below; re-theme text by re-pointing the token family, not by editing the component.

RoleRe-pointReach
.typography-body-sm
  • --typography-body-sm-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-body-sm-font-size clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem) via --font-size-150
  • --typography-body-sm-font-weight 350 via --typography-font-weight-regular → --font-weight-350
  • --typography-body-sm-letter-spacing 0.01em via --letter-spacing-default
  • --typography-body-sm-line-height 1.6 via --line-height-normal
+20 more components esa-alert-box, esa-breadcrumbs, esa-button-toggle, esa-card, esa-checkbox-group, esa-combobox, esa-command-palette, esa-date-picker, esa-empty-state, esa-file-upload, esa-form-field, esa-input-tag, esa-link-column, esa-progress-bar, esa-radio-group, esa-search-panel, esa-select, esa-stat, esa-text-field, esa-textarea

Wired to this component

None. This component has no hooks of its own — it themes entirely through the shared tokens below, so every override here also moves its siblings.

Shared tokens it reads

Owned by a family or by the semantic layer. Still overridable — but scope the override (esa-foo { --form-border-color: … }) unless you mean to move everything that reads it. Reach says what else that is.

Token Reach Resolves to
--loading-spinner-color component owned by esa-loading-spinner · +1 more #46a758 via --color-background-brand → --color-grass-9
--loading-spinner-track-color component owned by esa-loading-spinner · +1 more #d9d9d9 via --color-border-default-subtle → --color-gray-6
--animation-spin semantic every component 750ms via --duration-750
--color-background-elevation-raised semantic every component #fcfcfc via --color-gray-1
--color-background-overlay-scrim semantic every component rgba(255, 255, 255, 0.8) via --color-white-a-10
--color-content-default-secondary semantic every component #646464 via --color-gray-11
--radius-pill semantic every component 9999px via --radius-full
--spacing-300 primitive every component 0.75rem