Sync status
FreeWhether the work on screen is safe, in one pill that changes in place: saving, saved, offline with changes waiting, failed with a retry, or edited elsewhere.
pnpm dlx shadcn@latest add https://caliperui.site/r/sync-status.jsonWhen to use #
Use it for
- Editors, forms that save as you type, offline-capable apps.
- Anywhere people wonder whether a change was kept.
Skip it when
- Explicit save buttons; the button's own feedback is enough.
- Background jobs; use job runs or a toast.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/sync-status.json
Adds its 6 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above. It is built from savedAgo and SyncStatus.
import { useState } from "react" import { SegmentedControl } from "@/components/ui/segmented-control" import { SyncStatus, type SyncState } from "@/components/ui/sync-status" export function SyncStatusDemo() { const [state, setState] = useState<SyncState>("saved"); const [savedAt, setSavedAt] = useState(() => new Date()); const save = () => { setState("saving"); window.setTimeout(() => { setState("saved"); setSavedAt(new Date()); }, 900); }; return ( <div className="grid w-full max-w-lg gap-5"> <SegmentedControl className="justify-self-start max-w-full overflow-x-auto" label="State" value={state} onValueChange={(v) => (v === "saving" ? save() : setState(v as SyncState))} options={[{ value: "saving", label: "Save" }, { value: "saved", label: "Saved" }, { value: "offline", label: "Offline" }, { value: "error", label: "Fail" }, { value: "conflict", label: "Conflict" }]} /> <div className="flex items-center gap-3 rounded-surface border border-border bg-surface px-4 py-3"> <span className="min-w-0 flex-1 truncate text-sm font-medium">Q3 board pack</span> <SyncStatus state={state} savedAt={savedAt} pending={3} onRetry={save} onResolve={() => setState("saved")} /> </div> </div> ); }
API reference #
savedAgo
component
No props of its own; it accepts the native props of its root element.
SyncStatus
component
Accessibility #
It exposes status semantics. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.