focus-visible

A polyfill for the CSS :focus-visible pseudo-class, shown only for keyboard/assistive focus

Library
npm
v5.2.1
1,600stars
W3C

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
46/100Fair
Development Activity0
Maintenance20
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
69/100Good
Architecture62
Code Quality68
Innovation55
Learning Curve90

focus-visible is a small JavaScript polyfill for the (now-standardized) CSS :focus-visible pseudo-selector. It adds a focus-visible class to an element whenever the browser determines the focus ring should be shown — for example after keyboard navigation — while leaving it off when focus was triggered purely by a mouse click, letting authors style keyboard-focus rings distinctly from mouse-focus without suppressing accessibility affordances entirely.

The project was developed under the W3C Web Incubator Community Group (WICG) alongside the :focus-visible spec itself and was widely adopted during the years before all major browsers shipped native support. As of the current README, :focus-visible is now supported natively across Chrome, Edge, Firefox, Safari, and their mobile equivalents, so the maintainers explicitly recommend using the native CSS selector where possible and are not planning further releases — the polyfill remains available and stable for projects that still need to support older browser versions or want a consistent fallback.

What You Get

  • A focus-visible class applied only when keyboard-driven or otherwise ‘visible-focus-worthy’ focus occurs
  • Heuristics based on keydown-then-focus sequences (Tab, Shift+Tab, arrow keys) and always-visible-focus form elements
  • A data-js-focus-visible/data-focus-visible-added attribute mode for frameworks that overwrite element classes
  • An imperative applyFocusVisiblePolyfill() API for applying the polyfill to Shadow DOM roots
  • A focus-visible-polyfill-ready event for coordinating lazy-loaded usage

Common Use Cases

  • Suppressing mouse-click focus outlines while preserving them for keyboard navigation on older/legacy browser support matrices
  • Providing a consistent focus-ring behavior across browsers before universal native :focus-visible support existed
  • Applying focus-visible-aware styling inside Shadow DOM components via the imperative API
  • Defensive CSS fallback pattern (.js-focus-visible :focus:not(.focus-visible)) for design systems supporting a broad browser matrix

Under The Hood

Architecture: The entire polyfill lives in a single ~300-line source file (src/focus-visible.js) that attaches global keydown/focus/blur/mousedown listeners at the document level, tracks a ‘had keyboard event’ flag, and toggles a focus-visible class (or data-focus-visible-added attribute) on the currently focused element based on that flag plus a whitelist of form-like input types that should always show a focus ring. A rollup.config.js bundles this single source file into the distributed dist/focus-visible.js / .min.js UMD-style build.

Tech Stack: Vanilla JavaScript with no runtime dependencies; Rollup handles bundling, ESLint and Prettier (via lint-staged) enforce style on commit, and the package ships as a plain UMD/CommonJS-compatible bundle consumed via a <script> tag or node_modules import.

Code Quality: There’s a dedicated test/ directory with specs and fixtures subdirectories exercising the focus-heuristic behavior, plus historical Travis CI integration (badge in README). The license is listed as W3C in package.json (the GitHub API reports NOASSERTION, i.e. no auto-detected SPDX license, since W3C’s license text isn’t a standard SPDX identifier) which is standard for WICG-incubated specs/polyfills. With 38 contributors and 270 total commits, the project saw active development during the years the spec was being finalized, but the maintainers have explicitly frozen new feature work now that :focus-visible ships natively everywhere.

API Design: For the common case there is no API at all — just include the script and add one CSS rule (.js-focus-visible :focus:not(.focus-visible) { outline: none; }) — making adoption essentially zero-boilerplate. The only exposed programmatic surface (window.applyFocusVisiblePolyfill() and the focus-visible-polyfill-ready event) exists solely for the Shadow DOM edge case, keeping the default integration path extremely simple.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search