# Loading overlay

A pane of smoked glass laid over a panel while it works. The panel underneath keeps every pixel of its place, dimmed and a little out of focus, with one plate in the middle carrying a spinner and a line of words. Set aria-busy on the panel to lower the glass; what is underneath goes inert, so nothing in it can be pressed or tabbed to until the glass lifts.

Kit part · Free · https://tactileui.dev/parts/loading-overlay

- Element: `<section class="tui-panel tui-loading" aria-busy>`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: Loaders
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### Glass up · the panel as it is

```html
<div style="width:min(100%,340px)"><section class="tui-panel tui-loading" aria-busy="false" aria-label="Invoice 0412">
  <div class="tui-loading__content" style="display:grid;gap:14px;text-align:left">
    <div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Invoice 0412 · Northwind</span><span class="tui-status"><span class="tui-lamp" data-on></span>Draft</span></div>
    <div class="tui-field"><label class="tui-label" for="lo-up-c">Customer</label><input class="tui-input" id="lo-up-c" value="Harlow &amp; Finch Ltd"></div>
    <div class="tui-field"><label class="tui-label" for="lo-up-a">Amount</label><input class="tui-input" id="lo-up-a" value="£1,240.00"></div>
    <div style="display:flex;gap:8px;justify-content:flex-end"><button class="tui-key" type="button">Discard</button><button class="tui-key tui-key--primary" type="button">Save changes</button></div>
  </div>
  <div class="tui-loading__pane"><div class="tui-loading__plate" role="status" tabindex="-1"><span class="tui-spinner tui-spinner--sm"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label"></span></span><span class="tui-loading__text">Saving your changes</span></div></div>
</section></div>
```

### Glass down · aria-busy="true"

```html
<div style="width:min(100%,340px)"><section class="tui-panel tui-loading" aria-busy="true" aria-label="Invoice 0412">
  <div class="tui-loading__content" style="display:grid;gap:14px;text-align:left">
    <div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Invoice 0412 · Northwind</span><span class="tui-status"><span class="tui-lamp" data-on></span>Draft</span></div>
    <div class="tui-field"><label class="tui-label" for="lo-down-c">Customer</label><input class="tui-input" id="lo-down-c" value="Harlow &amp; Finch Ltd"></div>
    <div class="tui-field"><label class="tui-label" for="lo-down-a">Amount</label><input class="tui-input" id="lo-down-a" value="£1,240.00"></div>
    <div style="display:flex;gap:8px;justify-content:flex-end"><button class="tui-key" type="button">Discard</button><button class="tui-key tui-key--primary" type="button">Save changes</button></div>
  </div>
  <div class="tui-loading__pane"><div class="tui-loading__plate" role="status" tabindex="-1"><span class="tui-spinner tui-spinner--sm"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label"></span></span><span class="tui-loading__text">Refreshing the invoice</span></div></div>
</section></div>
```

### On a list of rows

```html
<div class="tui-panel tui-loading" aria-busy="true" aria-label="Recent invoices" style="width:min(100%,340px);padding:0"><div class="tui-loading__content"><ul class="tui-list"><li class="tui-list__row"><span></span><span class="tui-list__text"><span class="tui-list__title">Harlow &amp; Finch Ltd</span></span><span class="tui-muted">£1,240.00</span></li><li class="tui-list__row"><span></span><span class="tui-list__text"><span class="tui-list__title">Quay Joinery</span></span><span class="tui-muted">£310.00</span></li><li class="tui-list__row"><span></span><span class="tui-list__text"><span class="tui-list__title">Okafor Surveys</span></span><span class="tui-muted">£95.00</span></li></ul></div><div class="tui-loading__pane"><div class="tui-loading__plate" role="status" tabindex="-1"><span class="tui-spinner tui-spinner--sm"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label"></span></span><span class="tui-loading__text">Refreshing</span></div></div></div>
```

## Every state

- Glass up
- Glass down

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
| .tui-loading[aria-busy] | class | on the panel | The panel the glass goes over. aria-busy="true" lowers it; "false" lifts it. The pane is the panel’s own size, so nothing moves. |
| .tui-loading__content | element | inert while busy | The panel’s own content. kit.mjs makes it inert while the glass is down, so no pointer, key or Tab reaches it and a screen reader skips it. |
| .tui-loading__pane > .tui-loading__plate[role="status"] | element | with tabindex="-1" | The glass and the plate on it, a polite status region. The focus lands on the plate if it was inside the panel. |
| .tui-loading__text | element | one line | What is going on, in words. Change it while the glass is down and it is said again. |
| setBusy(panel, busy, message?) | function | needs kit.mjs | Lowers or lifts the glass, sets the line and keeps the content inert and the focus safe. aria-busy set by hand is watched too. |
| syncBusy(panel) | function | needs kit.mjs | Makes the content match aria-busy, for a panel you changed some other way. |

## Markup

```html
<section class="tui-panel tui-loading" aria-busy="false" aria-label="Invoice 0412">
  <div class="tui-loading__content">
    <!-- the panel, as it always is -->
  </div>
  <div class="tui-loading__pane">
    <div class="tui-loading__plate" role="status" tabindex="-1">
      <span class="tui-spinner tui-spinner--sm">
        <span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span>
      </span>
      <span class="tui-loading__text">Saving your changes</span>
    </div>
  </div>
</section>
```

```js
import { setBusy } from '/kit/kit.mjs';

const panel = document.querySelector('.tui-loading');

setBusy(panel, true, 'Saving your changes');   // glass down: content inert, the plate says why
await save();
setBusy(panel, false);                          // glass up: content live, focus returned

// aria-busy set by hand works too: kit.mjs watches it and keeps the content inert in step.
```

The demo on the page:

```html
<div style="width:min(100%,460px)"><section class="tui-panel tui-loading" aria-busy="false" aria-label="Invoice 0412" data-lo>
  <div class="tui-loading__content" style="display:grid;gap:14px;text-align:left">
    <div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Invoice 0412 · Northwind</span><span class="tui-status"><span class="tui-lamp" data-on></span>Draft</span></div>
    <div class="tui-field"><label class="tui-label" for="lo-demo-c">Customer</label><input class="tui-input" id="lo-demo-c" value="Harlow &amp; Finch Ltd"></div>
    <div class="tui-field"><label class="tui-label" for="lo-demo-a">Amount</label><input class="tui-input" id="lo-demo-a" value="£1,240.00"></div>
    <div style="display:flex;gap:8px;justify-content:flex-end"><button class="tui-key" type="button">Discard</button><button class="tui-key tui-key--primary" type="button" data-lo-save>Save changes</button></div>
  </div>
  <div class="tui-loading__pane"><div class="tui-loading__plate" role="status" tabindex="-1"><span class="tui-spinner tui-spinner--sm"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label"></span></span><span class="tui-loading__text">Saving your changes</span></div></div>
</section><div style="margin-top:12px"><output class="tui-muted" style="font-size:var(--tui-t-small)" data-lo-out aria-live="polite">Nothing saved yet</output></div></div>
```

## Usage notes

Press Save changes: the glass comes down over the invoice, the form behind it is out of reach (Tab skips it), and the plate says what is going on. After two seconds it lifts and the panel is exactly as it was. The two panels under Variants show the same panel with the glass up and down.

## Accessibility

- The panel carries `aria-busy`, which tells a screen reader its content is changing; the plate is a polite `role="status"`, so its line is said when the glass comes down and again when the words change.
- What is underneath is inert while the glass is down: it cannot be pressed, tabbed to or read, so nobody acts on a form that is mid-save. It is given back, with the focus, when the glass lifts.
- If the focus was inside the panel it moves to the plate, which has a visible 2px ring, and returns to where it was when the work is done.
- The glass dims what is under it, but the words are on an ordinary plate in ordinary ink (the same contrast as any other text), so they read the same in every finish. Where reduced transparency is asked for, the glass is solid.
- Under reduced motion the glass appears at once and the spinner holds on one lit lamp; the words are the same.

## How it’s built

The panel is given a second layer, an absolutely placed pane the size of the panel with the panel’s own corners, so the panel is neither replaced nor resized. The glass is the kit’s shadow colour at half strength with a little blur, a faint sheen from the upper left and a lit lip; the plate on it is a float plate, the same one a menu uses. The script does the one thing CSS cannot, making the content inert and looking after the focus.

## Rules

Show that a panel is saving or refreshing without moving it or blanking it: the same panel, behind glass, with one line saying what is happening.

The full rules are in `loading-overlay.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Spinner](https://tactileui.dev/parts/spinner): A ring of eight lamps lit in turn, a chase round a dial: each lamp is a dim cap with its light laid over it, and the light fades as the chase passes on, leaving a short tail. Only the light’s opacity moves. Amber, the lamp for work in hand. Under reduced motion it holds still on one lit lamp at the top, and the words still say it is busy.
- [Skeleton](https://tactileui.dev/parts/skeleton): The shape of a page before its data arrives, drawn as the unprinted plate: blank engraved bars where the words will print. Nothing on it moves. One amber lamp beside “Loading” is the only sign of work.
- [Panel](https://tactileui.dev/parts/panel): A face plate that groups what belongs together: a head, a rule, then the parts. Screws only on device-like panels.
- [Status](https://tactileui.dev/parts/status): A lamp with words: the state line in tables, panel heads and lists. The words carry the meaning; the lamp is the glance.
