# Loading overlay

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

## Anatomy

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](/loading-indicator.html.md) 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.

 Name

Save

```
<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-color` | surface at 70% |
| `--md-comp-loading-overlay-motion` | default effects spring |
| `--md-comp-loading-overlay-motion-duration` | the default effects spring's duration |
