A button is a small protocol

I learned to distrust tickets that begin with “add a button.” The visible control may take a few lines of code. The action behind it can involve validation, permissions, network state, duplicate protection, recovery, analytics, and accessibility.

A button is a small protocol between a person’s intent and a system side effect.

Mockups usually show the idle state. Production needs the rest of the model:

  • idle, with a label that names the action
  • focused, so keyboard users can find the control
  • pressed, with immediate input feedback
  • loading, with a rule for repeated input
  • successful, with a visible result
  • failed, with a recovery path
  • unavailable, with a clear reason

Write these transitions before polishing color and motion. The exercise exposes missing behavior while the design is still cheap to change. Give the request state one source of truth so the interface cannot promise an action the system is unable to perform.

Start with the HTML button element. It already supports keyboard activation, focus, form submission, and disabled semantics. A generic element makes the team rebuild and test those behaviors.

The label should name the outcome, such as “Create account.” An icon-only control needs an accessible name. A destructive action should identify the thing that will change.

Disabled behavior needs a product decision. A native disabled button leaves the tab order, so a keyboard user cannot focus it to learn what is missing. For an incomplete form, an available button that validates the input and points to the missing field may be clearer. A busy request, a missing permission, and incomplete input need different explanations.

A server request adds another state machine. The browser may send a request twice. The connection may disappear after the server commits the change. A late response may arrive after the user opens another record. A retry may repeat a payment or invitation.

The interface can prevent an accidental double click. The server must guarantee that an action meant to happen once actually happens once. Use an idempotency key or a domain uniqueness rule, and keep the request identity stable across retries.

Derive loading from the request itself. Preserve the label width so progress text keeps nearby content stable. Use aria-busy or a nearby live region when assistive technology needs an update.

Optimistic updates work when success is common and rollback has a clear meaning. Saving an item or changing a local preference often fits. Keep enough prior state to restore the view, show a useful error, and make a retry safe. Concurrent updates also need an ordering rule so an old failure cannot undo a newer success.

Payments, permission changes, and destructive actions need confirmed results and explicit progress because a false success can break trust.

Before release, test the button with a keyboard, screen reader, slow network, failed request, double click, narrow screen, and stale surrounding data. Most defects appear outside the idle state. A button feels simple when every state keeps the same promise as the system behind it.