Skip to content

Loading Overlay ​

SLoadingOverlay covers a region — a card, a table, a form — or the whole viewport while something loads, and keeps the pointer from reaching what is underneath. It draws a backdrop, a spinner and a label; the default slot replaces the spinner and the label with anything else, for example a circular SProgress with a percentage.

The overlay is positioned, not portaled: it fills the nearest positioned ancestor, so the covered element needs position: relative. Add the s-loading-overlay-host class to it instead of writing that rule yourself. With fullscreen the overlay becomes position: fixed and covers the viewport, while still rendering where it stands in the markup — no teleport, so the server output matches the client. It never locks page scroll: loading is not a modal state, and an overlay that silently froze the page would be harder to predict than one that leaves scrolling alone.

open is a plain prop, not a v-model. The component never closes itself — the flag belongs to the request being waited on.

Accessibility ​

The overlay carries aria-busy="true", and the label is a role="status" live region, so the busy state is announced rather than only seen. hide-label keeps that text for screen readers while leaving only the spinner visible.

What the overlay cannot do is stop the keyboard: the controls it covers stay in the tab order even though nobody can see them. Mark the covered content inert yourself — it is one attribute on the same flag:

vue
<script setup lang="ts">
import { ref } from 'vue'

const loading = ref(true)
</script>

<template>
  <div class="s-loading-overlay-host">
    <div :inert="loading">
      <!-- the content being covered -->
    </div>
    <SLoadingOverlay :open="loading" />
  </div>
</template>

The component does not apply inert itself. It would have to guess which of its siblings counts as "the content", and putting inert on the shared parent would take the overlay's own live region out of the accessibility tree together with the content — the busy state would stop being announced. Keep inert on the content wrapper, never on the element that holds the overlay.

Over a card ​

The host is the card: s-loading-overlay-host gives it the positioning context, and the overlay fills it edge to edge.

Monthly report
Revenue — $128,400
Orders — 312
Refunds — 4
Loading

Set spinner to false to keep only the text, hide-label to keep only the spinner, and size to scale both together.

Loading
Saving
Almost there

Without label the text comes from the locale dictionary (loading — "Loading"): override it on one instance with the prop, or everywhere through ConfigProvider.

Determinate progress in the slot ​

The default slot replaces the spinner and the label. A circular SProgress names itself through aria-valuetext, so the percentage is announced as it moves.

68%

Fullscreen ​

fullscreen covers the viewport. The overlay below closes itself after two seconds, so the page never stays covered — a real one closes when the request settles. It does not trap focus and does not lock scrolling; when the whole page has to be blocked for the keyboard too, set inert on the application root along with the flag.

Spinner color ​

color tints the spinner with a name from the palette — for an overlay that sits inside a themed area, or one that should read as a warning rather than routine work. The label keeps the text color: a tinted sentence on a near-opaque backdrop reads worse than a plain one.

Invoice #4021 — 8 items, paid on 12 May.
Default
Invoice #4021 — 8 items, paid on 12 May.
Teal
Invoice #4021 — 8 items, paid on 12 May.
Warning

Backdrop ​

blur frosts the covered content with backdrop-filter and lowers the dimming to 0.4, since blurred text is already unreadable. opacity sets the dimming explicitly, from 0 to 1; it wins over both defaults. The same value can be set from CSS through the --s-loading-overlay-opacity variable, and the blur radius through --s-loading-overlay-blur.

Invoice #4021 — 8 items, paid on 12 May.
Blurred
Invoice #4021 — 8 items, paid on 12 May.
Dimmed

API ​

Props

NameTypeDefaultDescription
openbooleanfalseWhether the overlay covers its region. The component never changes it: the loading state belongs to the request the consumer is waiting for.
fullscreenbooleanfalseCover the viewport instead of the nearest positioned ancestor. A regular overlay fills the closest positioned parent, so that parent needs position: relative — add the s-loading-overlay-host class to it.
labelstringundefinedText under the spinner. Defaults to the locale dictionary entry (loading).
hide-labelbooleanfalseKeep the label for screen readers only, leaving just the spinner visible.
spinnerbooleantrueShow the spinner above the label.
size"md" | "sm" | "lg""md"Size of the spinner, the label and the gap between them: sm, md or lg.
colorstringundefinedSpinner color: a name from the palette (primary/teal/teal-10). The label keeps the text color — the overlay covers content, and a tinted sentence on a near-opaque backdrop reads worse than a plain one.
blurbooleanfalseBlur the covered content behind the backdrop.
opacitynumberundefinedBackdrop opacity, from 0 to 1. Defaults to the --s-loading-overlay-opacity variable (0.9, and 0.4 with blur, where the blur already hides the content).

Slots

NameDescription
defaultReplaces the spinner and the label — a place for a circular SProgress with a percentage. The overlay keeps announcing itself as busy, but the replacement has to carry its own accessible name.