Accessible form structure

Keyboard and focus behavior

Preserve logical source order, visible focus, native keyboard interaction, and a predictable destination after submission or validation.

A form must work without a mouse. Plenty of people never touch one, and native controls already give you most of the keyboard behavior you need. Your job is to not break it.

Here is what the browser provides out of the box:

  • Tab moves to the next control, Shift+Tab moves back
  • Space toggles a checkbox
  • arrow keys move between the radio buttons in a group
  • Enter in a text field submits the form

Build custom controls and you have to reimplement all of this. Another good reason to use native elements.

Keep the DOM in order

The keyboard follows the document, not the layout. CSS Grid and Flexbox can put a control anywhere on screen, but Tab still walks through the HTML in source order.

So if your markup has the submit button before the last field, focus jumps from the field back up to the button. It looks right and works wrong. Fix the HTML order, not the CSS.

Never hide focus

Some people remove the focus ring because the design looks cleaner without it. That leaves keyboard users blind: they press Tab and have no idea where they are.

If the default outline clashes with the design, replace it with something just as visible:

:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 3px;
}

:focus-visible is the right selector here. It shows the ring for keyboard focus but skips it after a mouse click, which is exactly what designers usually wanted in the first place.

Don’t fight with tabindex

Positive tabindex values, like tabindex="3", force a custom tab order. They always cause trouble, because every new control has to fit into your numbering. Fix the HTML order instead.

tabindex="-1" is the one value I use. It makes an element focusable from script without adding it to the Tab sequence. That’s what you want for an error summary you focus after a failed submission.

After a failed submission

Keep every value the person typed. Making someone retype a form because one field failed is the fastest way to lose them.

For a long form with several errors, move focus to an error summary at the top that links to each invalid field. For a short form, the browser already focuses the first invalid control, and that’s often enough.

Don’t move focus for every small update, though. Focus jumping around while someone types is more confusing than helpful.

Try this on your own form: unplug the mouse, or just don’t touch it. Fill in and submit the whole form with the keyboard. Every control must be reachable, the focus ring must be visible at every step, and the submission must land somewhere useful.

Lesson completed