Level Access

Author: Level Access

A focus indicator is the outline or highlight, often called a focus ring, that marks which interactive element has keyboard focus as you move through a webpage with Tab. Focus indicators can help organizations meet several Web Content Accessibility Guidelines (WCAG) success criteria, including 2.4.7 Focus Visible (Level AA).

Somewhere in most codebases, there is a line of CSS that reads outline: none. There’s usually a story behind it. Maybe the browser’s default focus ring appeared on mouse click as well as keyboard navigation, a designer flagged it as a bug, and a developer removed it globally to close out the ticket. But this “fix” has consequences no one anticipated. A focus indicator is now missing, leaving keyboard users without an easy way to know where they are on the page.

So what exactly is a focus indicator, and what does WCAG, the global standard for web accessibility, say about how to build one properly? This article covers which success criteria apply and at what conformance level, how to design and build an indicator that meets them, and how to test it.

Key insights

  • A focus indicator is an outline or highlight that marks which interactive element on a webpage has keyboard focus. Browsers apply one by default, and it should never be removed without an accessible replacement.
  • Focus indicators benefit anyone navigating by keyboard, including people with low vision, people with motor disabilities, and people who use switch or voice input.
  • Three WCAG Level AA success criteria cover focus indicators: 2.4.7 Focus Visible, 1.4.11 Non-text Contrast, and 2.4.11 Focus Not Obscured (Minimum). The two CSS pixel size rule comes from 2.4.13 Focus Appearance (Level AAA).
  • A two-color focus indicator, with at least 9:1 contrast between its light and dark tones, meets 3:1 contrast against any solid background.
  • Use the CSS pseudo-class :focus-visible rather than :focus so the indicator appears for keyboard navigation but not on mouse click. Every major browser has supported it since March 2022.
  • Setting outline: none with no replacement is the most direct way to fail 2.4.7, and one of the least likely defects to be caught before release.
  • Automated tools can flag a missing indicator but can’t judge whether one that exists has enough contrast or is obscured, so testing takes a manual tab-through.

What is a focus indicator?

A focus indicator is the visible outline or highlight that shows which interactive element currently has keyboard focus as a user moves through a page with the Tab key. It’s also commonly referred to as a focus ring, visible focus, or a keyboard focus indicator, and the terms are interchangeable in practice.

Every major browser applies a default focus indicator automatically, marking each link, button, and form field in turn as you tab through a page. That default is the baseline WCAG expects you to preserve or improve on, not a starting point to clear away.

A focus indicator is different from a hover state. Hover follows the pointer and disappears the moment it moves. Focus follows the keyboard and persists until focus moves elsewhere.

For people who navigate by keyboard without a screen reader, the focus indicator is the only signal of where they are on the page. That includes many people with low vision, who find tracking a pointer at high magnification slow and imprecise, and people with motor disabilities who use a keyboard, switch device, or voice control. People with disabilities related to attention, short-term memory, or executive processing also benefit from finding focus quickly, especially in long forms.

What WCAG requires

Focus indicators are not governed by a single WCAG success criterion, and the requirements sit at two different conformance levels.

To meet WCAG at Level AA, a focus indicator must exist and be visible (2.4.7) and have at least 3:1 contrast against adjacent colors (1.4.11). Additionally, components must not be entirely hidden when focused (2.4.11). To conform at Level AAA, focus indicators must also meet rules for minimum size and contrast change (2.4.13), and no part of the component that receives focus can be hidden (2.4.12).

The following table breaks down what each Level AA and Level AAA success criterion requires.

Success criterion WCAG Level What it requires
2.4.7 Focus Visible AA A keyboard focus indicator is visible
1.4.11 Non-text Contrast AA 3:1 contrast against adjacent colors
2.4.11 Focus Not Obscured (Minimum) AA The focused component is not entirely hidden
2.4.12 Focus Not Obscured (Enhanced) AAA No part of the focused component is hidden
2.4.13 Focus Appearance AAA Minimum area, and contrast between focused and unfocused states

 

Success Criterion 2.4.7 is worth reading closely, because it’s narrower than most people assume. Specifically, it requires that “any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.” That’s it. An indicator either exists and is visible, or it doesn’t.

Guidance on color contrast, size, and thickness comes from other criteria. The 3:1 contrast requirement comes from 1.4.11 (Level AA), which applies to focus indicators because they convey essential visual information. The two-CSS-pixel perimeter requirement comes from 2.4.13 (Level AAA).

Most teams aim for Level AA conformance as a baseline, because meeting every Level AAA criterion isn’t always realistic or achievable. Even so, 2.4.13 is worth adopting: A two-pixel outline costs nothing to specify in a design token, and it pays off in easier navigation for users.

Focus order is a separate criterion that’s easy to confuse with focus visibility. 2.4.7 asks whether you can tell which element has focus, while 2.4.3 Focus Order asks whether focus moves in a logical sequence. A strong focus ring that jumps from header to footer and back still fails 2.4.3.

How to design an effective focus indicator

Once you know which criteria apply, most of the design work comes down to a few concrete decisions. Design against numbers rather than adjectives, because “clear and obvious” isn’t easily testable.

  • Contrast of at least 3:1: To pass 1.4.11, the indicator needs at least 3:1 contrast against the colors adjacent to it, usually both the control and the background behind it. A dark ring that passes on white will fail when the same button sits on a dark hero image, so check the contrast against every surface the component appears on.
  • A two-color indicator for mixed backgrounds: When a component appears on both light and dark surfaces, a single color rarely clears 3:1 against all of them. A two-color indicator pairs a light tone with a dark one. The Worldwide Web Consortium’s (W3C) technique C40 sets out the rule: If the two colors have at least 9:1 contrast with each other, at least one of them is guaranteed to meet 3:1 against any solid background. The guarantee holds only for solid colors, so make sure to check indicators over images and gradients individually.
  • At least two CSS pixels, and a clear change from the resting state: 2.4.13 asks for an area at least as large as a two-CSS-pixel-thick perimeter of the component, and a 3:1 contrast change between focused and unfocused states. A two-pixel outline around the full control meets the size rule. For a background tint, measure the difference between states rather than estimating.
  • One consistent style across the page: WCAG doesn’t require a single indicator style, but consistency makes a real difference. When a page uses a ring on buttons, an underline on links, a glow on cards, and a color change on menu items, people with low vision have to relearn how focus is marked every time it moves. Pick one treatment, apply it everywhere, and vary it only where a component genuinely needs a different approach.

Meeting 1.4.11 doesn’t automatically satisfy 2.4.13. One measures the indicator against surrounding colors, the other against the component’s own resting state.

How to build a focus indicator in CSS

A conformant focus indicator takes only a few lines of CSS. The pattern below uses outline for the main ring, box-shadow for the second tone, and :focus-visible so the indicator appears for keyboard users but not on mouse click. Copy it as a unit, then adjust the colors and thickness to your design tokens.

<button class="btn">Add to cart</button>
<a class="link" href="/cart">View cart</a>

/* Two-color focus indicator for keyboard and other non-pointer input */
:focus-visible {
  outline: 3px solid #1a1a1a;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #ffffff;
}

/* Suppress the ring for pointer input in browsers
   without native :focus-visible support */
:focus:not(:focus-visible) {
  outline: none;
}

Here’s how it works:

  • outline draws the main ring outside the element without affecting layout. A border would add width on focus and nudge the content around it.
  • 3px solid replaces the thin default ring and clears the 2.4.13 minimum with room to spare.
  • outline-offset: 2px opens a small gap between the control and the ring. Larger values can be clipped by containers that set overflow: hidden.
  • box-shadow fills that gap with white. With well over 9:1 contrast between the two tones, the dark ring works on light backgrounds and the white band works on dark ones.
  • :focus:not(:focus-visible) is the fallback. Browsers that don’t support :focus-visible skip both rules and keep their default indicator, so nobody ends up with nothing.

:focus vs. :focus-visible

:focus applies whenever an element receives focus, from any input method, so a mouse click leaves a ring on the button. To pointer users, this reads as a stuck state, which is the complaint that starts most teams down the path of removing focus styles entirely.

:focus-visible applies only when the browser’s heuristic determines an indicator is needed. In practice, the ring appears for keyboard and other non-pointer input but not on mouse click. Text inputs are the exception: Browsers show the indicator on click there too, because users need to know where typing will land.

Chrome and Edge have supported :focus-visible since version 86, Firefox since version 85, and Safari since 15.4 in March 2022. The fallback line above covers anything older.

How do you remove the focus outline for pointer users?

You don’t remove it. However, you may control when it shows.

Nobody wants a ring stuck on a button after a mouse click, but outline: none fixes that by removing the indicator for everyone, including the people who depend on it. The :focus-visible pattern above gives you the behavior you wanted without the trade-off, as long as you follow two rules:

  • Never write outline: none on its own: Scope it to :focus:not(:focus-visible). A bare outline: none, or a base-layer reset in a design system, removes the indicator with nothing in its place and fails 2.4.7 at Level AA.
  • Replace before you remove: Define your own indicator and confirm it meets 3:1 contrast before styling over the browser default, so no component ever ships without one.

Focus indicators in High Contrast mode and with reduced motion

Windows High Contrast mode, exposed to CSS as forced-colors, replaces author colors with the user’s palette and suppresses box shadows. That’s why the pattern above carries its main ring on outline: The white band disappears, but the ring remains. Set its color with a system keyword so it follows the user’s palette:

@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid Highlight;
  }
}

If your indicator animates, respect prefers-reduced-motion. A pulsing or sliding ring can be disorienting, particularly for people with vestibular disorders, so keep the indicator and drop the transition:

@media (prefers-reduced-motion: reduce) {
  :focus-visible {
    transition: none;
  }
}

Keeping focus clear of sticky headers and overlays

2.4.11 Focus Not Obscured (Minimum), a Level AA criterion added in WCAG 2.2, requires that a focused component not be entirely hidden by content you added to the page. Sticky headers are the usual culprit: Tab backwards, and the browser often scrolls the focused control directly underneath one. Cookie banners and chat widgets cause the same problem at the edges of the viewport.

The fix is usually one property. Set scroll-padding-top on the root element to match your sticky header’s height, and scroll-padding-bottom for any fixed footer or chat launcher.

Focus indicators on custom components

Native elements handle focus for you. A button, link, or input enters the tab order automatically and gets the browser’s default indicator. Build the same control from a div and it gets neither, so the indicator becomes your responsibility.

tabindex="0" puts a custom control into the tab order, where your :focus-visible rule applies. tabindex="-1" makes an element focusable by script only, which suits modal containers and error summaries. Never use a positive value, which pushes the element ahead of the natural order and makes the tab sequence unpredictable.

Composite widgets such as listboxes, tab lists, menus, and grids usually take a single tab stop, with arrow keys moving between the options inside. They handle focus in one of two ways:

  • Roving tabindex: DOM focus moves to each option, so your :focus-visible styles apply as normal.
  • aria-activedescendant: DOM focus stays on the container while the attribute points to the active option. The browser draws no indicator on that option, because as far as it’s concerned the option never received focus. You have to style it yourself, typically through a class or attribute your script updates alongside aria-activedescendant.

Either way, each option needs its own indicator. 2.4.13 reflects this through its sub-component rule, which applies the minimum area requirement to the focused option rather than the whole widget.

Custom checkboxes, radio buttons, and toggles raise a similar issue. When the native input is hidden and a decorative element takes its place, the input still receives focus but isn’t on screen, so the indicator has to move to the element that stands in for it, for example with input:focus-visible + label::before.

Finally, keep headings, paragraphs, and other static content out of the tab order. Focus should land only on things a user can act on.

How to test focus indicators

Testing for focus indicators can’t be fully automated. A scanner can flag an element with no indicator at all, but it can’t judge whether one that exists has enough contrast, stays consistent, or is hidden behind a sticky header.

The good news is that a manual spot check only takes about five minutes on most pages:

  • Tab through every control: Press Tab from the top of the page until you return to the browser chrome, then Shift+Tab back through. You should never lose track of where focus is. The backward pass is where sticky headers hide the focused control.
  • Check consistency as you go: Note whether the indicator style changes between components. Several treatments on one page usually means focus styles are set per component rather than once in the design system.
  • Check contrast on every background: Test against each surface the component appears on, not just the default one.
  • Arrow through composite widgets: In listboxes, menus, and comboboxes, confirm the active option is marked as you move through it. This is where aria-activedescendant implementations most often fall short.
  • Test in forced-colors mode and at 200% zoom: Tab through with Windows High Contrast turned on, then again at 200% zoom, where reflow can move components under fixed elements.

Automated scanning still earns its place. It finds missing indicators at scale, including a base-layer outline: none that a three-page manual pass would miss. Use it to narrow where to look, then confirm manually.

Common focus indicator mistakes and how to fix them

Most focus indicator failures come from a short list of recurring patterns, each with a specific fix.

  • outline: none with no replacement: This is the most direct way to fail 2.4.7. Scope the declaration to :focus:not(:focus-visible) instead.
  • Styling :focus instead of :focus-visible: The ring shows on mouse click, and teams respond by stripping focus styles globally. Style :focus-visible.
  • A focus color with too little contrast: Brand colors picked without measuring often fall short of 3:1 on some backgrounds. Measure against every surface, or use a two-color indicator.
  • Relying on a subtle color change: A faint background tint reads as a hover state. Pair it with an outline, or confirm the change meets the 3:1 contrast requirement.
  • Different indicators on different components: Mixed treatments make focus harder to track, particularly for people with low vision. Set one style in the design system.
  • Missing indicators on custom components: Custom widgets, hidden native inputs, and aria-activedescendant options get no browser default. Provide the indicator explicitly.
  • Focus hidden behind a sticky header or overlay: This fails 2.4.11. Set scroll-padding to match the height of fixed elements.
  • A ring clipped by overflow: hidden: A large outline-offset can push the ring past a container that clips it. Reduce the offset or adjust the container.

Build focus indicators into your design system

A focus indicator is one of the easiest accessibility wins available to design and development teams, and one of the easiest to delete by accident. The teams that get focus indicators right set them once at the design system level, where a single token carries the outline, the offset, the second tone, and the contrast floor to every control that inherits from it. That also solves consistency, because every component draws on the same indicator. Handled that way, the question shifts from whether the ring survives the next refactor to what else the system can standardize.

Level Access helps engineering and design teams build accessibility into the components and workflows they already use, so conformance holds as the codebase changes rather than degrading between audits. Our design and developer tools surface issues in Figma and in the pipeline, at the point where a focus style is written rather than months later in a report. To find out how that fits your stack, request a demo today.

Frequently asked questions

What is a focus indicator?

A focus indicator is the outline or highlight, often called a focus ring, that marks which interactive element has keyboard focus. Browsers apply one by default, and it should never be removed without an accessible replacement.

A visible focus indicator is one that clearly marks which element has keyboard focus, and it is required. WCAG Success Criterion 2.4.7 Focus Visible (Level AA) states that “any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.” Contrast and size rules come from other criteria.

Three Level AA criteria apply: 2.4.7 Focus Visible requires that an indicator exist, 1.4.11 Non-text Contrast requires 3:1 contrast against adjacent colors, and 2.4.11 Focus Not Obscured (Minimum) requires that the focused element not be entirely hidden. 2.4.12 and 2.4.13 add stricter rules at Level AAA.

Yes, at Level AA, under 1.4.11 Non-text Contrast rather than 2.4.7. The indicator needs 3:1 against adjacent colors on every background it appears on. The 3:1 change between focused and unfocused states is a separate Level AAA rule under 2.4.13.

At Level AA there is no minimum size. The rule people quote, an area at least as large as a two CSS pixel thick perimeter of the component, comes from 2.4.13 Focus Appearance (Level AAA). Even so, a two-pixel outline is a sensible baseline.

:focus applies whenever an element receives focus, including on mouse click. :focus-visible applies only when the browser determines an indicator is needed, typically for keyboard input, which makes it the recommended selector for focus styles.

Don’t remove it. Control when it shows instead, by styling :focus-visible so the indicator applies during keyboard navigation but not on mouse click. Removing the outline with no replacement is the most direct way to fail WCAG 2.4.7 at Level AA.

Every major browser supports it: Chrome and Edge since version 86, Firefox since version 85, and Safari since 15.4 in March 2022. For older engines, scope outline: none to :focus:not(:focus-visible) so unsupported browsers keep their default ring.

Only if you plan for it. Forced-colors mode overrides author colors and suppresses box shadows, so build the indicator on outline and set its color with a system keyword such as Highlight inside a forced-colors media query.

That’s the browser’s default focus indicator, and how it renders depends on your browser and operating system. On macOS, Safari draws a soft ring in the system accent color, which is blue by default, while Chrome and Edge use a dark ring paired with a white one, and Firefox’s default varies by platform. If the indicator appears on mouse click and you want it gone, switch your styles to :focus-visible rather than removing the outline.