← Back to Writing
5 min read

Keyboard Navigation Done Right


Tab through your product right now. If you have never done this, you will be surprised. Focus indicators disappear mid-flow. Modals trap focus. Dropdowns require a mouse. The keyboard experience is usually broken in ways that don't show up in automated audits.

Focus Indicators

The first thing most design systems do is outline: none. The reasoning is that the default blue ring is ugly. The effect is that keyboard users lose their location on the page entirely.

The fix is not to restore the default — it's to design a focus indicator that works with your visual system:

:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 3px;
}

:focus-visible only shows the indicator when the browser determines the user is navigating by keyboard, so mouse users never see it. This eliminates the "ugly ring on click" problem without removing keyboard support.

Focus Management in Dynamic UI

When a modal opens, focus must move into it. When it closes, focus must return to the trigger. When a route changes in a SPA, focus must move to the new page content.

None of this happens automatically. You have to manage it explicitly:

const triggerRef = useRef<HTMLButtonElement>(null);
const modalRef = useRef<HTMLDivElement>(null);

function openModal() {
  setOpen(true);
  // Focus moves into modal on next render
  requestAnimationFrame(() => modalRef.current?.focus());
}

function closeModal() {
  setOpen(false);
  // Return focus to trigger
  triggerRef.current?.focus();
}

Skip Links

A skip link is a visually hidden anchor at the top of the page that becomes visible on focus and jumps to the main content. Without it, keyboard users must tab through every nav item on every page load.

This is a ten-line implementation with significant impact. There is no reason not to have one.