State Transitions
State transitions animate the change between two versions of the same element — communicating what changed, how, and confirming that the user's action was received.
An element does not jump from idle to loading to success — it transitions. The animation between states is where the system communicates that the change is happening, what kind of change it is, and whether it succeeded.
What state transitions do
A state transition is an animation that plays between two defined states of the same element — a button going from default to loading, a toggle switching from off to on, a form field moving from neutral to error. The transition communicates continuity: this is the same element, and it has changed. Without the transition, the state change feels like a substitution — a different element replacing the first — which breaks the user’s sense of direct manipulation.
Button states
The button is the most common canvas for state transitions. A primary action button moving through default → loading → success → default tells a complete story without any additional UI. The loading state should transform the label into a spinner (or add one alongside it) while keeping the button at the same size — a button that changes size when it enters loading state causes layout shift. The success state should appear for a fixed duration (1–2 seconds) before returning to default, giving the user confirmation before the cycle resets.
Toggle transitions
A toggle that switches position with no animation looks like it jumped. A toggle that animates — the thumb sliding from one end to the other over 150–200ms — communicates that the state changed and shows the direction of the change. The translate of the thumb, combined with a background-colour change, creates a two-property transition that reinforces the binary nature of the state change.
Icon morphing
Some state transitions involve changing the icon within a control — a play button becoming a pause button, a bookmark becoming a filled bookmark, a menu icon morphing into a close icon. Done well, these transitions maintain visual continuity between the two states. Done poorly, they are distracting. Use them only for high-frequency, deeply familiar controls where the morphed pair is universally understood.
Interruption
State transitions must handle interruption gracefully. If a user clicks a loading button before it returns to default, the animation should not glitch or restart from the beginning. Use CSS transitions rather than scripted keyframe animations where possible — they handle interruption correctly by default, continuing from wherever they are.
CSS transitions vs @keyframes
These are two distinct CSS animation mechanisms and they are not interchangeable.
CSS transitions animate a property from its current value to a new value when that value changes — triggered by a state change (:hover, :focus, a class toggle). They are the right tool for state changes between two defined values: default ↔ hover, open ↔ closed, on ↔ off. They handle interruption correctly by default — if a user triggers the transition again before it completes, it reverses smoothly from wherever it is.
CSS @keyframes run a defined sequence of steps at defined percentages of the animation duration, regardless of current state. They are appropriate for: multi-step sequences, looping animations (spinners), and effects that need precise control over intermediate states. An interrupted keyframe animation jumps to its final state rather than reversing.
/* Transition: hover state change */
.button {
background: var(--colour-accent);
transition: background 150ms ease-out;
}
.button:hover { background: var(--colour-accent-dark); }
/* Keyframes: looping spinner */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner { animation: spin 600ms linear infinite; }
In practice: use transition for interactive state changes; use @keyframes for sequences, loops, and entrance animations triggered by a class addition. The toggle is a transition. The loading spinner is a keyframe. The modal entrance can be either — a keyframe tied to a class add is often more predictable.
State transitions and screen readers
Visual state transitions communicate change to sighted users. Screen reader users need the same change communicated through text. A button that visually transitions through loading → success is conveying three distinct states; a screen reader user receives none of those states unless they are announced.
Practical requirements:
Button state changes — when a button enters loading state, add aria-disabled="true" and update its accessible label or add a visually-hidden live region (“Processing your request…”). When the action completes, announce the outcome. The visual transition is not perceived by screen reader users; the announcement is the transition for them.
Toggle switches — role="switch" with aria-checked="true"/"false" is the correct ARIA pattern for toggles. The screen reader announces the new state on change. The visual animation is supplementary to the semantic state change; ensure the ARIA attribute update happens at the point the visual transition begins, not after it completes.
Form field validation states — when a field transitions from neutral to error state, the visual change alone (red border, error icon) is insufficient. The error message must be associated via aria-describedby and the field’s aria-invalid="true" state updated simultaneously. These state attributes are the screen reader’s equivalent of the visual colour and icon change.
The takeaway
Animate every state change on interactive elements — default ↔ loading, off ↔ on, neutral ↔ error. Keep transitions short (150–250ms), use composited properties, and ensure layout does not shift during the transition. Handle interruption by default rather than as an edge case.