squirrelscan
GitHub

focus-visible: keeping a visible keyboard focus ring

Removing the focus outline leaves keyboard users with no idea where they are. squirrel flags outline:none with no :focus-visible fallback.

What a focus indicator is

When a keyboard user tabs through a page the browser draws a ring around the focused element. outline: none deletes it, and without a replacement the user is navigating blind. WCAG success criterion 2.4.7 Focus Visible, Level AA, requires that the indicator exist.

:focus-visible is the modern way to restyle it. The browser decides when an indicator is warranted rather than matching every focus event, so in practice a keyboard user gets the ring and someone clicking a button does not. The heuristic is the browser’s, not the page’s: text inputs generally match on a mouse click too, and a user preference can force the indicator on everywhere.

What squirrel checks

This rule scans the page source as text, covering inline <style> blocks and style attributes. Rules in a linked stylesheet are not fetched, so they are invisible to it. Severity is warning.

  • focus-outline-global fails when the source contains a universal-selector rule that removes the outline, matching the shape * { ... outline: none } or outline: 0. Report message: Global outline removal detected, with the value * { outline: none } removes focus for all elements.
  • focus-outline warns when no global removal pattern matched, outline: none or outline: 0 appears anywhere, and :focus-visible does not. The two branches are exclusive, so a page with global removal never produces this warning. Report message: outline:none found - ensure alternative focus styles exist, with the value Consider using :focus-visible for custom focus styles.
  • focus-modern passes when :focus-visible or :focus-within appears, with the message Modern focus selectors in use and the matched selector as its value.
  • focus-visible returns info with Focus styles not analyzed (requires CSS inspection) when none of the above matched.

How to fix it

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

:focus:not(:focus-visible) {
  outline: none;
}

Keep both rules together: the first draws the ring where the browser judges an indicator useful, the second removes the default ring everywhere else. Between them nobody navigating by keyboard is left without an indicator.

Because the match is textual, outline: none inside a comment or a minified vendor bundle is still reported. The reverse is more common: a site whose focus styles live in an external stylesheet returns info even when the styles are correct.

Rule IDa11y/focus-visible
CategoryAccessibility
ScopePer-page
Severitywarning
Weight5/10

Enable / disable

Disable this rule

[rules]
disable = ["a11y/focus-visible"]

Disable all Accessibility rules

[rules]
disable = ["a11y/*"]

Enable only this rule

[rules]
enable = ["a11y/focus-visible"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

  • a11y/tabindex: positive tabindex values that scramble the order the ring moves in.
  • a11y/aria-hidden-focus: elements that take focus while hidden from assistive technology.
  • a11y/skip-link: letting keyboard users jump past repeated navigation.
  • a11y/color-contrast: text against its background, the contrast check squirrel does run. It does not measure the ring itself.

Accessibility findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Accessibility section of the report. Global outline removal is reported first, since it affects every control. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close