Skip to content

State machines and reactions

A form can look complete yet leave the team guessing what happens after Submit: does it wait, reject invalid input, preserve values after failure, or allow a second request? A clickable prototype usually shows just one of these paths. A DFC Bridge reaction contract describes all relevant paths, including feedback, focus, history, and recovery.

  • A reaction is event + current state + guard → effects + next state.
  • A state is a stable condition of a component, form, flow, or page.
  • A view ([view=...]) is a page/data fixture such as default, loading, empty, or error.
  • A target is the stable element, state, modal, form, route, or service operation referenced by a reaction.

Do not confuse a screenshot named “success” with a complete success reaction. The contract also needs the submitting state, response handling, focus destination, announcement, repeat behavior, and navigation/history result.

Existing tags locate interaction anchors:

email [field=email] [name=email]
send [action=submit:lead-form]
lead-form [state=lead-form-idle]
success [state=lead-form-success]

Prototype connections may supply additional evidence. Do not add a tag for every event, guard, timeout, focus destination, announcement, and HTTP status. Put those relationships in structured DFC Bridge metadata keyed by stable identities.

A reaction should answer all fields that apply:

Field Meaning
id Stable reaction identity.
scope Component, form, page, application, or cross-route flow.
from / event Current state and initiating user/system event.
guard Condition that allows or rejects the transition.
effects Validation, request, storage, analytics, clipboard, or other side effects.
to Success state; include failure/cancel/timeout branches.
feedback Visible status and assistive-technology announcement.
focus Focus destination and restoration rule.
history URL, query, hash, navigation, replace/push, and Back/Forward result.
concurrency Duplicate input, cancellation, stale response, and ordering policy.
persistence What survives reload, route change, closing, or another device.

Non-standalone module fragment. This excerpt shows only bridge.interaction and intentionally omits required envelope fields. Insert it into the required bridge envelope from the transfer contract before exchange or full-contract validation.

{
"bridge": {
"interaction": {
"stateMachines": [{
"id": "lead-form",
"initial": "idle",
"states": ["idle", "invalid", "submitting", "success", "failure"],
"transitions": [{
"id": "submit-lead",
"from": ["idle", "invalid", "failure"],
"event": { "type": "submit", "source": "lead-form" },
"guard": "all-required-fields-valid",
"pending": { "to": "submitting", "duplicateEvent": "ignore", "cancel": "on-route-leave" },
"outcomes": {
"success": { "to": "success", "focus": "success-heading", "announce": "Lead sent" },
"validation": { "to": "invalid", "focus": "first-invalid-field", "announce": "Form has errors" },
"failure": { "to": "failure", "focus": "error-summary", "retry": "preserve-values" }
},
"history": "none"
}]
}]
}
}
}

Read the example as a flow: submit from an allowed state, check the guard, show submitting, then follow the response branch. A failure preserves values for retry; a success moves focus to its heading. The identifiers belong to the adapter-facing contract, not to a longer Figma layer name.

Scope Examples Source of truth
Element focused, pressed, selected, invalid Native semantics or UI Kit component
Component accordion open, dialog busy, date picker month UI Kit state model plus instance content
Form pristine, dirty, validating, submitting, success Form reaction machine
Page/data loading, empty, partial, error Root [view] fixtures plus data contract
Application signed out, offline, permission changed Cross-page flow contract

Do not create a page root for every hover state. Do not hide a page-wide error inside a button variant. Parent and child machines may interact, but ownership and event propagation must be explicit.

Record semantic events—activate, submit, change, dismiss, select, drag, timeout, response—rather than a device-specific gesture alone. Then define supported inputs:

  • click/tap activation also works with the native keyboard behavior;
  • hover-only content also appears on keyboard focus and can be dismissed;
  • drag operations have a single-pointer, keyboard, or control-based alternative;
  • long press, swipe, and multi-touch gestures have discoverable alternatives;
  • repeated activation while pending follows an explicit concurrency rule.

Follow the interaction and keyboard conventions in the official WAI-ARIA Authoring Practices Guide, while preferring native HTML semantics when available.

For every field define:

  • data name, value type, required/optional status, and autocomplete purpose where applicable;
  • persistent visible label, instructions, accepted format, and constraints;
  • pristine, focused, filled, invalid, disabled, and read-only presentation;
  • validation timing: on input, blur, step change, or submit;
  • canonical error code/message and relationship to the field;
  • normalization that does not silently change meaning;
  • dependent fields, conditional visibility, and what happens to hidden values.

For the form define:

  • client and server validation authority;
  • error summary, first-invalid-field focus, and announcement;
  • pending UI and which actions remain available;
  • value preservation on error, authentication expiry, route change, and retry;
  • success result, duplicate submission handling, and idempotency expectation;
  • reset/undo confirmation and loss-of-unsaved-work behavior.

Placeholder text is not a label. Disabled controls cannot explain why they are unavailable; provide nearby status or instruction. Read-only and disabled are different product states.

Every request path includes idle → pending → success | empty | partial | failure | cancelled | timed-out as applicable. Specify:

  • whether prior content remains visible and whether it becomes stale;
  • progress behavior and minimum/maximum wait before changed messaging;
  • retry policy, backoff, offline queueing, and manual retry;
  • cancellation when query, route, modal, or component instance changes;
  • race policy: latest request wins, ordered application, merge, or reject;
  • deduplication and idempotency for repeated actions;
  • optimistic update, rollback, conflict resolution, and undo window;
  • status announcement without noisy repetition.

A spinner is not an async contract. A late response must not overwrite a newer query or move focus unexpectedly.

For dialogs, drawers, menus, popovers, and disclosures, define:

  • opening event and allowed states;
  • initial focus or reason focus stays on the trigger;
  • whether the surface is modal;
  • Tab/Shift+Tab scope and arrow-key rules where relevant;
  • Escape, close control, outside click, and destructive-dismiss behavior;
  • focus restoration to the trigger or a declared successor if the trigger disappears;
  • nested overlay and route-change behavior;
  • scroll locking without losing the previous scroll position.

Content hidden visually must not remain accidentally operable. Content merely collapsed for presentation must not lose required state. Use the matching APG pattern as implementation guidance, not as a substitute for the product decisions above.

A complete reaction says where focus goes when:

  • validation fails;
  • a modal opens or closes;
  • content is inserted, deleted, reordered, or filtered;
  • a step completes;
  • the focused item disappears;
  • a route changes or history is restored;
  • an operation fails, times out, or is undone.

Default to preserving focus when the user can continue in place. Move it only to support the next task or expose important feedback. Never reset focus to the document start as an accidental rendering side effect.

Declare whether state is shareable, bookmarkable, and restorable. For filters, tabs, pagination, selected records, drawers, and multi-step flows decide:

  • path, query, hash, in-memory, or persistent storage;
  • push versus replace history behavior;
  • Back/Forward result;
  • direct-load and refresh initialization;
  • invalid or expired state handling;
  • scroll and focus restoration;
  • whether closing an overlay reverses a history entry.

A deep link must initialize the same product state without replaying preceding clicks. The URL must not expose secrets or sensitive form values.

Before handoff, enumerate at least:

Path Required evidence
Happy path Start, intermediate feedback, final state, focus/history result
Invalid input Field and summary feedback, correction path
Empty result Explanation and recovery action
Permission/auth failure Preserved work and re-auth/request-access path
Network/server failure Retriable vs terminal behavior
Timeout/offline Cached state, queued work, reconnect behavior
Cancellation/dismissal Side-effect cleanup and focus restoration
Duplicate/rapid input Deterministic concurrency outcome
Back/Forward/reload State, focus, and scroll restoration

Before handing off this flow, verify the following. This is a review of the reaction contract, not an automatic Ready result for the page or application:

  • every interactive anchor has an action or real navigation destination;
  • every action resolves to a target and a complete reaction record;
  • all reachable states, including pending and failure, have designs or an inherited component contract;
  • fields have labels, constraints, errors, and value-preservation behavior;
  • async races, duplicate events, cancellation, retry, and rollback are decided;
  • focus, announcements, URL, history, and scroll behavior are explicit;
  • keyboard, pointer, touch, and assistive-technology users can complete the same task.

Zoom and scroll to read details. Esc closes.