# Error state

What a view shows when loading failed. It is the empty state’s sibling: the same tray, with the outline of what should be there printed on its floor, but the lamp in its title is lit red, the words say in plain language what happened and whether anything was lost, and the first key is Retry, with a way round it beside it if there is one. Pressing Retry holds the key, turns the lamp amber and has a screen reader say so.

Kit part · Free · https://tactileui.dev/parts/error-state

- Element: `<div class="tui-empty tui-error-state">`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Covers: Error state, Load failed
- Categories: Empty states
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### Slots · a list

```html
<div class="tui-empty tui-error-state" style="width:min(100%,300px)"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The invoices did not load</h2><p class="tui-empty__text">The server took too long to answer. Nothing was lost.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Retry</button></div></div>
```

### A sheet · a file or a report

```html
<div class="tui-empty tui-error-state" style="width:min(100%,300px)"><div class="tui-empty__outline tui-empty__outline--sheet" aria-hidden="true"></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The report would not open</h2><p class="tui-empty__text">The file may have moved. Check the link, or ask whoever shared it.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Try again</button></div></div>
```

### A plot · a chart

```html
<div class="tui-empty tui-error-state" style="width:min(100%,300px)"><div class="tui-empty__outline tui-empty__outline--plot" aria-hidden="true"></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The chart did not load</h2><p class="tui-empty__text">The readings are safe. They are just not reachable right now.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Retry</button></div></div>
```

### With a way round it

```html
<div class="tui-empty tui-error-state" style="width:min(100%,320px)"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>You are offline</h2><p class="tui-empty__text">Changes stay on this device until you are back online.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Check again</button><a class="tui-key tui-key--flat" href="#status">Keep working offline</a></div></div>
```

### Nothing to retry · the page is gone

```html
<div class="tui-empty tui-error-state" style="width:min(100%,300px)"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>This project no longer exists</h2><p class="tui-empty__text">It was deleted by its owner on 2 October.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><a class="tui-key tui-key--flat" href="#status">Back to projects</a></div></div>
```

### Retrying · lamp amber

```html
<div class="tui-empty tui-error-state" aria-busy="true" style="width:min(100%,300px)"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The invoices did not load</h2><p class="tui-empty__text">The server took too long to answer. Nothing was lost.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry aria-disabled="true">Retrying…</button></div></div>
```

### Side by side · .tui-empty--row

```html
<div class="tui-empty tui-error-state tui-empty--row" style="width:min(100%,520px)"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i></div><div class="tui-empty__words"><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The schedules did not load</h2><p class="tui-empty__text">Nothing was changed.</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Retry</button></div></div></div>
```

## Every state

- Slots
- Sheet
- Plot
- Retrying
- Key focused

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-empty.tui-error-state | class | on | The empty state’s tray with the error’s changes on it. Use both classes; it follows every finish and both volumes as the empty state does. |
| .tui-empty__outline | class | aria-hidden="true" | The outline of what failed to load: slots, a sheet (--sheet) or a plot (--plot), as in the empty state. Leave it out if there is nothing to draw. |
| h2.tui-status.tui-error-state__title > .tui-lamp[data-lamp="red"][data-on] | element | the title | What failed, in plain words, with the red lamp lit. Use the heading level that fits the page. |
| .tui-empty__text | class | on | What happened and whether anything is lost. No codes, no blame, one or two short sentences. |
| .tui-error-state__detail | class | optional, on | Small print for something a person may need to quote, such as a reference. |
| button[data-error-retry] | element | the first key | Retry. A press holds the key (aria-disabled, focus stays), changes its words to “Retrying…”, turns the lamp amber and fires tui-error-retry. |
| .tui-error-state__said[role="status"] | element | empty, in the markup | The polite region that says “Retrying” and, afterwards, what you pass to setRetrying(). |
| .tui-error-state__actions | class | the keys | Retry, and perhaps one flat key beside it for a way round: a status page, working offline. |
| setRetrying(tray, on, said?) | function | needs kit.mjs | Holds or releases the key from a script. Releasing puts its words back and says what you pass. |
| retryWords(label) | function | pure | The words on a held key: “Retry” gives “Retrying…”, “Try again” gives “Trying again…”. |
| tui-error-retry | event | bubbles | Fired on the tray when Retry is pressed. The page does the work and replaces the tray or releases the key. |
| role | attribute | "alert" only mid-task | Add it when the failure appears after the person did something. None for a view that fails on load. |

## Markup

```html
<!-- The empty state's tray. Name the failure; role="alert" only if it appeared after an action. -->
<div class="tui-empty tui-error-state">
  <div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div>
  <h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The invoices did not load</h2>
  <p class="tui-empty__text">The server took too long to answer. Nothing was lost.</p>
  <span class="tui-error-state__said" role="status"></span>
  <div class="tui-error-state__actions">
    <button class="tui-key tui-key--primary" type="button" data-error-retry>Retry</button>
    <a class="tui-key tui-key--flat" href="/status">Check the status page</a>
  </div>
</div>
```

```js
import { setRetrying } from '/kit/kit.mjs';   // wires every [data-error-retry]

tray.addEventListener('tui-error-retry', async () => {
  // The key is already held and the lamp amber.
  try { show(await load()); }                                  // success: replace the tray with the view
  catch { setRetrying(tray, false, 'Still not loading.'); }     // failure: put the key back, say so
});
```

The demo on the page:

```html
<div class="tui-panel" style="width:min(100%,540px);display:grid;gap:16px;text-align:left"><div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Invoices · Northwind</span></div><div data-er-host><div class="tui-empty tui-error-state"><div class="tui-empty__outline" aria-hidden="true"><i></i><i></i><i></i></div><h2 class="tui-status tui-error-state__title"><span class="tui-lamp" data-lamp="red" data-on aria-hidden="true"></span>The invoices did not load</h2><p class="tui-empty__text">The server took too long to answer. Nothing was lost, and your drafts are safe.</p><p class="tui-error-state__detail">Reference LD-4417</p><span class="tui-error-state__said" role="status"></span><div class="tui-error-state__actions"><button class="tui-key tui-key--primary" type="button" data-error-retry>Retry</button><a class="tui-key tui-key--flat" href="#status">Check the status page</a></div></div></div>
  <output class="tui-muted" style="font-size:var(--tui-t-small)" data-er-out aria-live="polite"></output></div>
```

## Usage notes

Press Retry: the key holds, the lamp goes amber and the page says it is trying. The first try fails again; the second loads the list. The key keeps the focus the whole time.

## Accessibility

- The title is a heading, so a screen reader user can find what failed from the heading list. The words say what happened; the red lamp is hidden from a screen reader and is only a second sign.
- Use `role="alert"` only when the failure appears after an action, such as a save, so it interrupts. A view that fails on load is simply read in its place.
- Tab reaches Retry and Enter or Space presses it. Retry keeps the focus. The key is `aria-disabled`, not `disabled`, while it works, so the person is not thrown out of it, and the polite status region says “Retrying” and, if it fails again, what you tell it to.
- The key says what it does: “Retry”, or “Check again” when the cause is the connection. A way round is a link or a flat key with its own words, never a bare “Learn more”.
- The lamp turns amber and blinks while retrying; the blink stops under reduced motion and the lamp stays lit amber. Keys are 44px on a phone and the tray never scrolls sideways.

## How it’s built

No new tray. It is `.tui-empty` with a lit lamp and an added row for the keys, so the recess, the printed outline and the way each finish draws them are the empty state’s own. The held key is the kit’s `aria-disabled` key with its words swapped by about thirty lines of script, and the lamp turns amber because the tray says `aria-busy`, which is also what a screen reader hears.

## Rules

Say what failed in plain words, say whether anything is lost, and give the one key that tries again.

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

## Related parts

- [Empty state](https://tactileui.dev/parts/empty-state): What a view shows before it has anything: an empty tray with the outline of what goes in it printed on its floor, one sentence and one key.
- [Alert](https://tactileui.dev/parts/alert): A message let into the surface, with an annunciator: a small lit legend (Note, Done, Check, Fault) beside the words that say what happened.
- [Key](https://tactileui.dev/parts/key): Every button, and every link that acts like one: a cap on a skirt that drops below the plate when pressed.
