Sync Status
betaWhere a save stands, beside what changed: saving, saved, waiting for a signal, or failed, with a retry icon.
Preview
saving
saved
offline
failed
size
sm
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/sync-status
Use it for
- Beside what changed: a card header, next to a form's buttons, under a total that just changed.
- Put it in a field's status slot, like FieldFrame's or an editor's, with size sm. It sits on the label line, 16px tall.
- offline counts what waits to send: Offline · 3 changes will send when you're back online. Apply changes on screen at once anyway. This line is the promise they'll send.
- failed says why in plain words with reason, like No signal, and onRetry ends the line with a retry icon.
- saved is quiet. Pass at, like 18:04, to say when.
Avoid it for
- A toast for a save. The status sits beside what changed.
- A status on a row or in a table. Use StatusText.
- Saying Saved after every keystroke. Show saving while a save runs, then saved.
Props
- state
Required. Where the save stands.
"saving" | "saved" | "offline" | "failed"
- count
offline: how many changes wait to send.
number
- at
saved: when, like "18:04".
string
- reason
failed: why, in plain words, like "No signal".
string
- onRetry
failed: a retry icon ends the line.
() => void
- retryLabel
Names the retry icon, in its tooltip and to screen readers.
string · default "Try again"
- size
sm sits on a field's label line: meta type, 16px tall. md is body type, and lg is section type.
"sm" | "md" | "lg" · default "md"
- className
Extra classes on the line. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| state | "saving" | "saved" | "offline" | "failed" | - | Required. Where the save stands. |
| count | number | - | offline: how many changes wait to send. |
| at | string | - | saved: when, like "18:04". |
| reason | string | - | failed: why, in plain words, like "No signal". |
| onRetry | () => void | - | failed: a retry icon ends the line. |
| retryLabel | string | "Try again" | Names the retry icon, in its tooltip and to screen readers. |
| size | "sm" | "md" | "lg" | "md" | sm sits on a field's label line: meta type, 16px tall. md is body type, and lg is section type. |
| className | string | - | Extra classes on the line. Later classes win. |
Accessibility
- The words sit in a polite status region, so a change is said once, without taking focus.
- The state is said in words: Saving, Saved, Offline, Couldn't save. Color only marks it.
- The retry icon is a button named by retryLabel, which also shows in its tooltip. It sits outside the status region, so its name is not said with every change.
- The icons are hidden from screen readers, since the words say the same.
Examples
Saving
<SyncStatus state="saving" />Offline
<SyncStatus state="offline" count={3} />Failed, with a retry
<SyncStatus state="failed" reason="No signal" onRetry={() => save()} />