caliper uidocs

Sync status

Free

Whether 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.

Installs components/ui/sync-status.tsxDepends on lucide-reactMotion shared motion tokens
pnpm dlx shadcn@latest add https://caliperui.site/r/sync-status.json
Q3 board packSaved just now

When 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 #

terminal
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.

example.tsx
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
PropTypeDefaultDescription
state *SyncState—
savedAtDate—
pendingnumber0Changes waiting to upload while offline.
onRetry(() => void)—
onResolve(() => void)—
classNamestring—

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.