Skip to content

SaveState

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/save-state.json
See it liveBeside a field, the three states, and running.→

A form without a Save button still has to say what it did. Otherwise the reader is left guessing whether their edit survived, and the usual answer to that guess is pressing Ctrl+S at a page that has no such thing.

const status = useSaveStatus(mutation.isPending, mutation.isError)
<SaveState
status={status}
savingLabel={t('save.saving')}
savedLabel={t('save.saved')}
/>

The tick decays. One that never leaves stops meaning “just now” and becomes furniture — part of the layout the reader stops seeing, which is the opposite of what an indicator is for. linger controls how long it stays.

Only a real save earns one. The hook watches the falling edge of the mutation, so mounting beside one that is already idle shows nothing. Without that, arriving at a page would flash a tick for a save that happened before the reader got there.

A failure is not a save. Pass isError and the tick is skipped: the failure is already being announced by a toast or an error on the field, and saying “saved” underneath that is worse than saying nothing at all.

It holds its width when idle. The line sits next to a field; if it grew and shrank with its own text, the layout would twitch on every save. It goes transparent rather than away.

One live region, announced politely. The ring is drawn from Spinner’s variants rather than by using Spinner, which carries its own role="status" — nesting live regions gives a screen reader two announcements for one event. And polite rather than assertive, because interrupting someone mid-sentence to say a field saved is how people learn to turn a screen reader’s verbosity down.

The words are yours. A primitive with a string of its own cannot be translated.

Props

Read from the types in ui/save-state.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

The indicator itself.

PropTypeDefault
statusrequiredidle | saving | savedWhat to show. Usually from useSaveStatus.
savingLabelReactNodeWhat to say while saving. The product's word, in the product's language.
savedLabelReactNodeWhat to say once it is saved.
classNamestring

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 15 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/save-state.tsx - the file a product copies - newest first.

  1. v0.20.0
    • feat five components for the harder half of a form cfb317a