TWISTEdBRACKETS

UI/UX law

// Interaction & Speed;

Focus Ring

A focus indicator is the keyboard equivalent of a mouse cursor: it only needs to appear for keyboard and other non-pointer input, it has to be genuinely visible against its background, it has to follow the DOM rather than the layout, and a modal has to hold it captive until the modal closes.

Mechanism

Why it happens

A mouse user always knows where they are because the pointer shows it; a keyboard user has no cursor at all, so the focus ring is the only signal of where the next keystroke lands, and hiding it strands them. :focus-visible resolves the usual complaint that a ring "appears on every click": it asks the browser to distinguish how focus arrived and shows the ring for keyboard and programmatic focus while suppressing it for a mouse click, instead of forcing one policy on both populations.

That ring still fails as a signal if it's too thin or too close in colour to read, which is why WCAG 1.4.11 sets a 3:1 non-text contrast floor for it.

It fails again if it never lands where expected: CSS properties like order, flex-direction, or grid placement change what a sighted user sees without touching the DOM, but Tab and Shift+Tab always walk the DOM's source order, so a visually reordered layout sends focus jumping around the screen in a sequence nobody chose.

A modal compounds all of it at once, since the page behind it is still in the DOM and still tabbable once the dialog opens, so without an explicit trap, Tab walks straight through the dialog and out into a page the user can no longer see.

Impact

Why it matters

  • A ring that appears on every mouse click reads as broken and gets "fixed" by removing it outright, which strands every keyboard user at once
  • A ring under the WCAG 1.4.11 contrast floor, or too thin or too close to the element, is functionally invisible even when it's technically present
  • CSS that reorders content visually (order, flex-direction: row-reverse, grid placement) without reordering the DOM sends Tab focus jumping around the screen out of visual sequence
  • An open modal with no focus trap lets Tab walk straight through it into the page behind it, which a keyboard user can no longer see

Example

Without vs. with

Without

Delete file?

↳ Tab escapes past Delete, no ring anywhere, into the page behind it

A "Delete file?" dialog opens, but Tab still walks focus out through the page behind it after the last button, and Escape leaves focus lost on <body>, so a keyboard user has to tab back in from the top of the page.

With

Delete file?

⟲ Tab loops Cancel → Delete → Cancel, Escape returns focus to the trigger

The same dialog moves focus to "Cancel" the instant it opens, Tab and Shift+Tab cycle only between its two buttons while it's open, and Escape (or either button) closes it and returns focus to "Delete file," the control that opened it.

Checklist

How to apply it

Style focus with :focus-visible, not :focus, so a mouse click stays quiet and keyboard or programmatic focus still shows the ring

Give the ring at least 2px of width, at least 2px of offset from the element, and at least 3:1 contrast against its background (WCAG 1.4.11)

Keep visual order and DOM order the same; if a reorder is unavoidable, verify Tab order still matches what's on screen

Trap Tab and Shift+Tab inside an open modal, close it on Escape, and return focus to the element that opened it

Recipe

Code example

Where it shows up

Element areas

ButtonsFormsStates