Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Design, Before Any Software

Hierarchy, type, colour and spacing — the part of design you can do with a pencil, before any software.

Lesson 69 of 749 min

Every state you did not draw still ships

The list

A component exists in more conditions than the one in the mockup. For anything interactive:

default        resting
hover          pointer over it (does not exist on touch)
focus-visible  keyboard focus (does not exist for mouse users)
active         being pressed
disabled       not available
loading        working
selected       currently chosen, in a set
error          in an invalid condition
read-only      shown but not editable
visited        for links

Every one of these ships whether or not somebody designed it. Undesigned, they ship as the browser's defaults, which are inconsistent across platforms, or as nothing at all, which means the interface does not respond to being used.

Hover and focus are different audiences

A point that gets conflated constantly. Hover does not exist on touch devices. Any information revealed only on hover is invisible to most of your users, which is why tooltips as the only explanation of an icon is a failure and why hover-only menus are a recurring accessibility complaint.

Focus does not appear for mouse users, and should not — a focus ring after every click was the original reason people removed focus styles. :focus-visible gives the ring to keyboard users and not to mouse clicks, which resolves the conflict entirely and is the correct selector to use.

So the two states serve different people, need different treatments, and neither substitutes for the other.

Disabled is the one that goes wrong

Disabled controls are conventionally shown at low contrast to signal unavailability, and WCAG explicitly exempts disabled controls from the contrast requirements. That exemption is real, it is also widely criticised, and it produces a genuine problem: a disabled button that nobody can read is a control whose label cannot be checked, by anybody, including people with perfect vision in bright light.

The deeper problem is that disabled controls communicate nothing about why. A Submit button that is greyed out tells the user they cannot proceed and not what to fix. They then hunt.

Three better patterns, in order of how often they apply:

  1. Keep the control enabled and explain on activation. The user presses Submit, and the form shows what is missing and moves focus there. This is the strongest pattern for forms and it is now common.
  2. Say why next to the control. If it must be disabled, put the reason in text beside it, not in a tooltip that touch users cannot reach.
  3. Hide it entirely, where the action is not merely unavailable but irrelevant in this context.

If you do keep a disabled state, make it legible anyway. The exemption permits low contrast; nothing requires it.

Loading, and the width problem

A button that replaces its label with a spinner changes width, which shifts everything beside it. The user's finger is already moving.

Fix it by reserving the width: keep the label in place at reduced opacity and overlay the spinner, or set an explicit minimum width so the shortest state and the longest state are the same size. This is the layout-shift point from the previous block, at component scale.

Also decide what the button does while loading. The correct answer is almost always that it stops accepting presses, because the common failure is a user tapping three times and submitting three orders.

Error is a component state, not a screen

An invalid field needs: a changed border, an icon, a message, and — from the colour block — none of those carrying the meaning alone. The message goes next to the field, not at the top of the form, because a message at the top makes the user hunt for which field it refers to.

The field should also keep what the user typed. Clearing an invalid field is the single most disliked behaviour in form design, and it is still common.

Specify them together

The practical method is a small table of decisions per component, drawn once:

Button, primary
  default   surface: --colour-action        label: --colour-on-action
  hover     surface: --colour-action-hover
  active    surface: --colour-action-active, translate-y 1px
  focus     ring: 2px --colour-focus, offset 2px
  disabled  surface: --colour-action-muted, cursor default
  loading   label at 0% opacity, spinner centred, width locked

Six rows. It takes ten minutes, it removes a week of small inconsistencies later, and every value in it is a token from the earlier lesson rather than a number.

The one thing to keep

A component ships in every state whether or not it was designed, hover and focus serve different audiences and never substitute for each other, and a disabled control that cannot be read or explained should usually be an enabled control that explains what is missing on activation.

Before you move on

A signup form disables Submit until every field is valid. Users report getting stuck without knowing why. The team proposes adding a tooltip on the disabled button explaining what is missing. What is the flaw?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly