Focus Visible WCAG 2.2: A Front-End Implementation Guide
Sidharth Nayyar

Sidharth Nayyar

Ready to make your website accessible? Engage with our team or start a free trial today.
Tap to ask AI about this article
You can ship a checkout, dashboard, or account page that looks polished and still fail the moment someone tabs through it. The usual culprit is not a broken component, it's the chrome around it, a sticky header, a consent banner, a chat launcher, or a floating action button that quietly covers the focused control. In a focus visible WCAG 2.2 audit, that's the first thing to fix, because keyboard users need a visible path through the interface, not just technically focusable elements.
TL;DR and the Layered Focus System in WCAG 2.2
The clean mental model is layered. 2.4.7 Focus Visible is the baseline, focus must be visible when a control can take keyboard focus. 2.4.11 Focus Not Obscured (Minimum) protects that focus from being hidden by sticky UI. 2.4.12 Focus Not Obscured (Enhanced) tightens the rule so the whole focused component stays visible. 2.4.13 Focus Appearance turns the outline itself into a measurable shape with clear geometry and contrast.
A lot of teams still treat focus as a single CSS problem. It isn't. One outline: none can take down the visible indicator, and a sticky banner can make a perfectly styled button functionally invisible the moment it receives focus. That's why the right answer is not “add an outline somewhere,” it's to build a layered system that protects visibility, prevents obscuration, and gives design a passable focus treatment that survives real browsers and real layouts.
On a checkout page, this shows up fast. You tab from the shipping form to the payment button, and the cookie banner sits right on top of the button. The page still works, but the keyboard user loses orientation. That's the difference between an interface that's technically usable and one that supports navigation.
For teams that already care about conversion, this is a CRO issue as much as an accessibility one. A focus state that can't be seen creates hesitation, misclicks, and abandonment. It also gives you a clear internal target for procurement and compliance, because WCAG 2.2 is what many public-sector and enterprise frameworks point to for accessibility expectations.
WCAG 2.2 keeps 2.4.7 Focus Visible as the baseline requirement and defines it as a condition where any keyboard-operable user interface must have a mode in which the focus indicator is visible. That moved the discussion from “does this look okay?” to something teams can audit against geometry and contrast thresholds, which is why implementation owners can review it as a component contract rather than a loose design preference. The W3C's understanding note also makes the visible indicator measurable, with the indicator at least as large as a 2 CSS pixel thick perimeter and with a 3:1 contrast ratio against the unfocused state, which gives engineering and QA a concrete test target. WCAG 2.2 focus visible understanding note

The cleanest way to read the WCAG 2.2 set is as one layered system. 2.4.7 answers whether focus can be seen at all. 2.4.11 answers whether sticky UI or overlays hide it. 2.4.12 answers whether the full focused element stays visible. 2.4.13 answers whether the indicator itself has enough area and contrast to count as a real signal. A design system can pass the first rule and still fail the others if the ring is too thin, too faint, or clipped by layout.
Practical rule: if keyboard users can't tell where they are after one Tab press, the problem is structural, not cosmetic.
The W3C release notes keep 2.4.7 Focus Visible in place while adding 2.4.11 Focus Not Obscured (Minimum), 2.4.12 Focus Not Obscured (Enhanced), and 2.4.13 Focus Appearance in WCAG 2.2. That split matters because modern UI failures rarely come from one source. A sticky header can hide the target, a modal shell can clip the ring, and a custom outline can fail the contrast and thickness checks all at once. WCAG 2.2 release notes
For teams auditing a design system, that is the useful mental model. Fixing focus visibility is not a single CSS tweak. It means checking browser defaults, checking any custom :focus treatment, and checking how sticky chrome, scroll containers, and overlays interact with the focused element in real use. The focus indicator explained for developers page is a useful companion reference for that part of the review.
For a broader implementation checklist that connects focus states to other usability checks, website accessibility best practices is a useful reference point because it treats focus visibility as part of the interaction layer rather than a one-off audit item.
The most common 2.4.11 Focus Not Obscured (Minimum) failure is simple. A user tabs to a link or button, and sticky UI sits on top of it. The usual suspects are sticky headers, cookie banners, chat widgets, and floating action buttons. WebAbility.io's guidance on this criterion describes the same pattern, focused elements must not be completely hidden by overlays, sticky headers, consent banners, or floating UI. skip link focus management gives a useful related pattern for keeping keyboard movement predictable when the top of the page is crowded.

For AA, the focused component can be partially visible, but it can't be completely hidden by author-created content when it receives focus. That matters because a sticky header can cover the top half of a target and still leave enough exposed for keyboard users to orient themselves. A fully hidden control, by contrast, gives no visual feedback at the exact moment focus lands, which is the failure mode teams usually see in QA.
The fastest way to catch this is boring and reliable. Tab through the page with sticky elements pinned, screenshot each focus state, and review the captures at the same viewport size your users see. If the element appears under the header, banner, or launcher, don't argue about intent. Move the layout.
scroll-padding-top is the first tool I reach for on the html element and on scrollable containers. It gives the browser room to bring the target into view without pinning it under the sticky chrome.
html { scroll-padding-top: 5rem; } .scroll-region { overflow: auto; scroll-padding-top: 5rem; } Avoid placing position: sticky on wrappers that sit directly over focus targets. If the design needs a persistent bar, reserve space for it rather than letting it float over content. Chat widgets should be configured to avoid overlapping the main content area, and the launcher should respect a minimum offset from the viewport edge so it doesn't sit on top of controls near the bottom corner.
A sticky element that looks harmless in a mockup can become a focus trap in production if it shares the same viewport space as primary actions.
A good team pattern is to package this into Storybook. Build a page with a pinned header, a cookie banner, and a keyboard path through the content. Then keep the screenshot set as a regression check so the fix survives the next layout refactor.
The difference between 2.4.11 and 2.4.12 is scope. Under 2.4.11 Focus Not Obscured (Minimum), at least part of the focused component has to remain visible. Under 2.4.12 Focus Not Obscured (Enhanced), the focused component has to be fully visible, with no part hidden by author-created content. That makes 2.4.11 the practical AA target for overlap scenarios, while 2.4.12 is the stricter AAA bar. WCAG 2.2 new success criteria commentary lays out that split clearly.
A sticky promotional banner over a call-to-action is a good example. If the top edge of the button is slightly tucked under a banner but the rest of the button and its focus state remain visible, that may still clear AA under 2.4.11. The same layout fails 2.4.12 because AAA expects the whole component to be unobscured. That distinction matters when product teams are deciding whether a particular screen should be designed to AA or whether a stricter treatment is worth the effort.
The fix depends on intent. If the page needs a banner, move the content area down or create a reserved safe zone. If the focused item sits inside a scroll container, use scroll-margin-top on the target so browser scrolling doesn't place it under the sticky area. If the issue is a highly dynamic panel, revisit the sticky behavior before you push more padding into every card.
A quick release check helps keep the decision honest:
The most common mistake is treating partial visibility as “close enough” without naming the target level. Teams move faster when the requirement is explicit, because they stop debating semantics and start changing container behavior.
2.4.13 Focus Appearance is the WCAG 2.2 criterion that turns the focus ring into a geometry problem. The visible indicator has to occupy an area at least as large as a 2 CSS pixel-thick perimeter of the unfocused component, and it has to maintain a 3:1 contrast ratio between focused and unfocused states for the same pixels. The W3C understanding note is explicit about both thresholds, and the release notes tie that requirement into the broader WCAG 2.2 focus system. WCAG 2.2 focus appearance understanding note
A ring that looks fine in Figma can still fail in the browser. It may be too thin, clipped by an overflow: hidden ancestor, or too close in color to the surrounding surface. A two-color ring or a thicker perimeter-style outline is usually the most reliable path, especially on rounded controls where a box-shadow offset can preserve the visual edge without collapsing into the button fill.
.button { border-radius: 9999px; outline: none; } .button:focus-visible { outline: 2px solid #ffffff; box-shadow: 0 0 0 4px #337eee; } That pattern gives you a clear perimeter, a strong contrast edge, and enough separation to survive complex backgrounds. The white inner outline and blue outer ring work together, which is exactly what custom controls need when the surrounding UI is busy.
The other trap is overflow. If a parent container clips the ring, the indicator can lose the perimeter area it needs. That's why focus styles need to be tested where they live, not just in an isolated component sandbox.
For teams that want implementation support beyond the component library, UI UX design services are often where focus tokens, layout spacing, and interaction states get aligned before a redesign ships.
Use :focus-visible as the default for custom keyboard focus styling. It applies when focus should be visible, which usually means keyboard navigation, while :focus applies whenever an element receives focus, including mouse clicks. That distinction matters in a design system, because not every control should flash a ring on pointer interaction, but some controls, like text inputs, often should carry a visible focus treatment in every modality.
The browser default can be enough when the author hasn't changed it. WCAG materials also note that the focus appearance criterion is assessed when the author has modified the default effect, which means a native button or link with an untouched browser outline may already satisfy the baseline expectation. Once you replace that default, the burden shifts to your CSS.
.control { border: 1px solid var(--border-subtle); border-radius: 0.5rem; } .control:focus-visible { outline: 2px solid var(--focus-inner); box-shadow: 0 0 0 4px var(--focus-outer); } .control:focus:not(:focus-visible) { outline: none; box-shadow: none; } That pattern keeps keyboard focus clear without adding noise to pointer clicks. It also gives you a token-driven entry point for theming, so high-contrast modes can swap --focus-inner and --focus-outer without changing component logic.
The rule is only useful if it survives future commits. Add a lint rule that blocks outline: none unless there's a documented replacement, then wire a Storybook a11y check into CI so components that lose their focus indicator fail the pull request. Axe-linter is useful here as an automated gate, but the value comes from pairing it with a visual regression path that shows the focus state in context.
The best design systems treat focus as a token, not an afterthought. Once the token exists, every button, link, tab, and menu item inherits the same accessible baseline, and product teams stop inventing one-off rings that behave differently from page to page.
The fastest way to lose keyboard usability is to make focus look neat in screenshots instead of legible in use. outline: none on buttons is the classic mistake, because it deletes the browser's safe fallback and forces every component to earn its own indicator. A low-contrast ring is the second mistake, especially when the control itself is small and the surrounding surface is also neutral. mastering tabindex from WebAbility.io is a useful companion read when a team is also cleaning up keyboard order and custom control behavior.

outline: none without a replacement: removes the visible fallback and often breaks 2.4.7 and 2.4.13 at the same time.The replacement pattern is straightforward. Keep a tokenised :focus-visible ring, use a box-shadow offset for rounded controls, and leave the outline in place unless you've proven the replacement passes. If the browser default already looks clear and the author hasn't modified it, keep it. That's often the safest option on simple links and native form controls.
If a design system can't show where focus is without designer intervention, it isn't finished yet.
The other reason this matters is that “visible” still carries edge cases. W3C's own discussion around focus visibility shows that the standard leaves room for interpretation in subtle cases like custom controls and clipped components. That's another reason to prefer bold, measurable rings over delicate visual tricks.
Start with a single cleanup pass. Verify 2.4.7 on every keyboard-operable component, confirm that overlays don't obscure focus, choose whether the product aims for 2.4.11 or 2.4.12 on high-stakes screens, audit 2.4.13 geometry and contrast, lock :focus-visible into the design system, and add the lint rule that blocks outline removal without a replacement. The WebAbility.io compliance checklist is a practical reference if you want to map those fixes against a broader WCAG 2.2 AA audit.
The harder part is keeping the work from drifting as new components ship. A regression in one new modal or banner can undo a month of careful cleanup, especially when different teams own headers, overlays, and navigation patterns. A continuous monitoring setup helps here because it keeps focus-indicator regressions visible alongside other WCAG findings, instead of leaving them buried in ad hoc QA notes.
For teams already operating with a design system and release pipeline, the next move is to make focus a contract. Add keyboard screenshot tests to Storybook, run the accessibility checks in CI, and route failures to the team that owns the component rather than the team that happened to notice it. That keeps remediation specific, which is what gets fixes shipped instead of debated.
If you're auditing a live product right now, start with the page where conversion matters most, then move through overlays, focus rings, and custom components in that order. The work is surgical, but the payoff is broad: clearer keyboard navigation, fewer hidden controls, and a design system that holds up under real use.
If you want to turn this into a repeatable process, WebAbility.io gives you a way to scan pages, track WCAG 2.2 focus issues, and keep the regression loop tight as components change. Visit WebAbility.io to see how continuous monitoring, audit trails, and implementation support can keep your focus states visible, measurable, and ready for release.