Skip to content

System & states

The states that decide whether a product feels finished.


82. EmptyState

Three variants, and they are genuinely different components:

Variant Illustration Content
first-run Isometric spot, 160px "No transactions yet" + explanation + one primary action
filtered None "No transactions match these filters" + "Clear filters"
period None "No transactions between 1–30 Sep 2026" + "Widen the period"

An illustration on the filtered variant is condescending — the user knows exactly what happened and wants the fix, not a picture.

Always exactly one clear next action. An empty state with no action is a dead end.

description is one line. The particulars — which three filters are active, what the import actually does before anything posts — go in detail, behind the ⓘ on the title. An empty state is read at a glance.

Illustration entrance: back-to-front, 60ms stagger, 8px translate, 320ms --ease-out, total capped at 480ms, never loops. Spec.

83. ErrorState

Couldn't load transactions. ⓘ        ← the technical account, one press away
No transactions were affected.       ← the financial reassurance
[Try again]  [Contact support]

The reassurance sentence is the most important line, and it is the only prose that stays on screen. In a financial product, an error makes users worry that something happened to their money. Saying explicitly that nothing did is worth more than the retry button — and worth more than the paragraph explaining the outage.

The detail — which endpoint, which timeout, which missing rate — is real and belongs to the person who asks for it, so it rides an InfoTip on the title rather than a third paragraph. An InfoTip and not a Tooltip: it opens on press, so the account of the failure survives touch.

Variants: network · permission · not-found · server · stale-data. Each states what happened, what it means for their money, and what to do.

84. Skeleton

Structural, matching the exact shape, position, and radii of the content it replaces. Slow breathe (opacity 0.6↔1, 1.6s) permitted; no shimmer sweep — a translating gradient across a full-page skeleton is large-surface paint animation and the clearest template tell there is.

A skeleton that doesn't match causes a jump when content lands, and that jump is worse than no skeleton at all.

85. StaleIndicator

"Last synced 12m ago · Retry". Appears on any surface showing cached or offline data. Dims the affected values to 0.7.

Stale financial data presented as current is the most dangerous state in the product. This component exists so that state is impossible to miss.

86. Toast

Sonner, configured to our tokens. 400ms ease enter, 200ms exit, transitions not keyframes, stack offsets −0.05 scale / −16px / −0.2 opacity, swipe-dismiss at 45px or velocity > 0.11. Auto-dismiss pauses on tab hide.

Never the only channel for a critical error. Toasts are timed and dismissible; a failed payment persists inline.

87. CommandPalette

Zero animation. Opens instantly. Used hundreds of times a day — this is the canonical Tier 0 component and the clearest demonstration of the frequency law.

Money-aware search: typing 1240 finds transactions with that amount; typing >1000 filters by magnitude; typing a date range parses it. Grouped results, recents first, full keyboard navigation.

88. Sheet

Vaul, configured. The primary overlay on phone — a sheet enters from the thumb zone; a centered dialog puts its actions in the middle of the screen.

320ms --ease-drawer enter, 225ms exit, 1:1 finger tracking, spring.gesture release, velocity-aware dismissal, boundary damping, pointer capture, multi-touch protection. Snap points for partial heights. overscroll-behavior: contain. Footer padded to max(24px, env(safe-area-inset-bottom)).

89. HoldToConfirm

clip-path: inset() fill, 1500ms linear press, 200ms --ease-out release, scale(0.97) on the button. Slow where the user is deciding, fast where the system is responding.

Accessible alternatives are mandatory: Space to hold with aria-live progress at 0/50/100%, Escape to abort, and a typed-confirmation fallback. Gating a financial action behind a motor-skill requirement is an accessibility failure, so hold-to-confirm is never the only path.

90. DensityToggle

compact / default / comfortable. Persisted per user, synced across devices, clamped to comfortable on phone regardless of the stored preference — 32px rows with 13px text fails both touch targets and arm's-length legibility.

Sets data-density on a container; every list component reads it.

The 9 components specified here.

Built against this document

The surfaces that implement what this argues — running, computing, or shaped by it.

Told when this spec changes

The docs are the contract the components are built against. When one changes, the component changes with it — and the copy in your repo does not. One email when that happens.

One address, no list rental, unsubscribe in one click.

Works in your QuickBooks Online or Xero file

Get your books closed every month

Lopifi is building AI bookkeeping that keeps your books closed on US GAAP as transactions happen, with a CPA review every quarter, for $200 a month. We’re not open yet.

$200/month when we open. No card now.