Focus & Keyboard

Make Every Interactive Thing Reachable

Focus & Keyboard

Keyboard users navigate with Tab. Every interactive element needs a visible focus indicator and a logical tab order.

4 min read Level 2/5 #css#accessibility#focus
What you'll learn
  • Style `:focus-visible` properly
  • Set tab order with `tabindex`
  • Test keyboard navigation

Some users navigate entirely with the keyboard — Tab to move between interactive elements, Enter / Space to activate. Every interactive element needs a visible focus indicator and a logical tab order.

:focus-visible — Smart Focus Rings

The browser’s default focus ring is ugly but ESSENTIAL for keyboard users. Removing it (outline: none) without a replacement breaks accessibility.

The modern approach uses :focus-visible to show the ring only when focus came from the keyboard:

button:focus { outline: none; }   /* drop the default */

button:focus-visible {
  outline: 2px solid blue;
  outline-offset: 2px;
}

Click with a mouse → no ring. Tab to it → ring appears. Best of both worlds.

What’s Focusable

By default, these elements are focusable:

  • <a href> — links with href
  • <button>
  • <input>, <select>, <textarea>
  • <iframe>, <audio controls>, <video controls>
  • Anything with tabindex (see below)

A <div> is NOT focusable — use a real element.

tabindex

ValueEffect
tabindex="0"Focusable, in tab order
tabindex="-1"Programmatically focusable (.focus()) but skipped by Tab
tabindex="1"+Positive — DON’T USE. Reorders tab flow, almost always wrong

For a custom control that needs focus, tabindex="0". For a container that should accept programmatic focus (a modal, error summary) but not normal tab order, tabindex="-1".

Tab Order

The default tab order is DOM order. Visual order (with flex’s order or grid item placement) doesn’t affect tab order. If they diverge, keyboard users get a confusing experience.

The fix: write your HTML in the intended tab order. CSS can reorder visually, but you usually shouldn’t.

<a href="#main" class="skip-link">Skip to main content</a>
.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

Hidden until focused with keyboard. Lets keyboard users jump past the header.

When a modal opens:

  1. Move focus into it
  2. Trap focus inside while open (Tab cycles within)
  3. On close, return focus to the trigger

This is JS-heavy. Modern dialog patterns or <dialog> element handle it for you.

Test It

The simplest accessibility test: unplug the mouse. Tab through the whole page.

  • Can you reach every interactive element?
  • Can you see where focus is?
  • Does Enter / Space activate buttons / links?
  • Can you escape from modals?

If any answer is no, fix it.

Up Next

Respecting users who don’t want motion.

Reduced Motion →