npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scroll-focus-polyfill

v1.1.0

Published

A tiny polyfill for browsers that cannot focus a scrollable element (notably affecting keyboard accessibility of horizontally scrollable <pre> blocks in some environments).

Readme

scroll-focus-polyfill

Package version GitHub last commit Release OpenSSF Scorecard

A tiny polyfill for browsers that cannot focus a scrollable element (notably affecting keyboard accessibility of horizontally scrollable <pre> blocks in some environments).

Why?

Some browsers, especially older versions or specific rendering engines, do not allow scrollable elements to receive keyboard focus. This can create accessibility issues, particularly for horizontally scrollable <pre> blocks or other scrollable containers that should be navigable via keyboard.

This polyfill automatically detects if the browser has this limitation and applies a fix by adding tabindex="0" to scrollable elements, making them keyboard accessible.

Installation

via npm

npm install scroll-focus-polyfill

via unpkg.com (CDN)

You can import the polyfill directly from unpkg.com without any build step:

<!-- Auto-execute version (detects and applies automatically) -->
<script src="https://unpkg.com/scroll-focus-polyfill"></script>

<!-- ES Module version -->
<script type="module">
  import 'https://unpkg.com/scroll-focus-polyfill';
</script>

Usage

Quick Start (Automatic)

The simplest way to use the polyfill is to just import it. It will automatically detect if it's needed and apply itself:

HTML Script Tag

<script src="https://unpkg.com/scroll-focus-polyfill"></script>

Or with npm:

<script src="node_modules/scroll-focus-polyfill"></script>

ES Modules

// Auto-executes on import
import 'scroll-focus-polyfill';

CommonJS

// Auto-executes on require
require('scroll-focus-polyfill');

With Options (Data Attributes)

You can configure the polyfill using data attributes on the script tag:

<!-- Enable debug logging -->
<script src="https://unpkg.com/scroll-focus-polyfill" data-debug="true"></script>

<!-- Force application (skip browser detection) -->
<script src="https://unpkg.com/scroll-focus-polyfill" data-force="true"></script>

<!-- Custom selectors -->
<script src="https://unpkg.com/scroll-focus-polyfill" data-selectors="pre, .scrollable, [data-scroll]"></script>

<!-- Disable per-element resize observation -->
<script src="https://unpkg.com/scroll-focus-polyfill" data-observe-resize="false"></script>

<!-- Combine multiple options -->
<script src="https://unpkg.com/scroll-focus-polyfill" 
        data-debug="true" 
        data-force="true"
        data-selectors="pre, .code-block"></script>

Manual Control

If you want full control over when and how the polyfill is applied, use the function export:

// Import the function export (does NOT auto-execute)
import { applyPolyfill } from 'scroll-focus-polyfill/fn';

// Apply with default options
applyPolyfill();

// Apply with custom options
applyPolyfill({
  debug: false,         // Enable debug logging (default: false)
  force: false,         // Force polyfill even if browser supports focus (default: false)
  observeResize: true,  // Re-evaluate elements when their size changes (default: true)
  selectors: ['pre']    // CSS selectors for elements to make focusable (default: ['pre'])
});

// Example: Enable debug logging
applyPolyfill({ debug: true });

// Example: Apply to custom elements
applyPolyfill({ selectors: ['pre', '.scrollable', '[data-scroll]'] });

// Example: Force application with custom selectors
applyPolyfill({ 
  force: true, 
  selectors: ['pre', '.code-block'] 
});

Entry Points

The package provides two entry points for different use cases:

| Entry Point | Auto-Execute | Use Case | |------------|--------------|----------| | scroll-focus-polyfill (default) | ✅ Yes | Quick plug-and-play with automatic detection | | scroll-focus-polyfill/fn | ❌ No | Full control over application timing and options |

Examples

// Default: Auto-execute with detection
import 'scroll-focus-polyfill';

// Function export: No auto-execute, full control
import { applyPolyfill } from 'scroll-focus-polyfill/fn';
applyPolyfill({ debug: true });

// Force mode via function export
import { applyPolyfill } from 'scroll-focus-polyfill/fn';
applyPolyfill({ force: true });

HTML Script Tag Usage

<!-- Default: Auto-execute with detection -->
<script src="https://unpkg.com/scroll-focus-polyfill"></script>

<!-- Force mode via data attribute -->
<script src="https://unpkg.com/scroll-focus-polyfill" data-force="true"></script>

<!-- With options via data attributes -->
<script src="https://unpkg.com/scroll-focus-polyfill" 
        data-debug="true" 
        data-selectors="pre, .scrollable"></script>

Options

  • debug (boolean, default: false): Enable console logging for debugging
  • force (boolean, default: false): Force the polyfill to apply even if the browser natively supports focusing scrollable elements
  • observeResize (boolean, default: true): Watch matching elements with a ResizeObserver so tabindex stays correct when an element's own box changes size. Set to false on pages with very large numbers of matching elements and call refresh() manually instead
  • selectors (array, default: ['pre']): CSS selectors for elements that should be made focusable when they have scrollable content

Manual refresh

applyPolyfill() returns a handle with a refresh() method that re-evaluates every matching element. This is useful when observeResize is disabled, or after a layout change the polyfill cannot detect:

import { applyPolyfill } from 'scroll-focus-polyfill/fn';

const polyfill = applyPolyfill({ observeResize: false });

// After your own layout change (panel toggle, split view drag, etc.)
polyfill.refresh();

How it Works

  1. Detection: The polyfill checks if the browser needs it by testing if a <pre> element with scrollable content can receive focus
  2. Application: If needed (or if force: true), it adds tabindex="0" to matching elements that have scrollable content
  3. Observation: It monitors the DOM for new elements and applies the fix automatically. Because content added, edited or restyled inside a matching element changes its scrollWidth/scrollHeight without changing its own box, every mutation also re-evaluates its matching ancestors. Child list, characterData and attribute changes are all observed, so text edits and class/style changes are covered
  4. Re-evaluation: It also re-checks elements when sizes change, adding tabindex="0" when a scrollbar appears and removing it again when the content no longer overflows. A single ResizeObserver watches each matching element, so changes caused by a sibling, sidebar or grid track are detected even when the viewport stays the same size. Elements removed from the DOM are unobserved automatically

Elements that already had a tabindex before the polyfill ran are never modified. Internal ownership state tracks attributes added by the polyfill; the data-scroll-focus-polyfill marker alone is never treated as proof of ownership. The polyfill removes its own tabindex and marker values only while they remain unchanged, so application-managed values are preserved.

Browser Support

This polyfill works in all modern browsers and will only activate if needed. It uses:

  • MutationObserver for DOM monitoring
  • ResizeObserver (when available) and the resize event for size changes
  • Standard DOM APIs for element detection

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for detailed information about:

  • Development setup
  • Available scripts
  • Commit guidelines
  • Pull request process
  • Code style

This project uses:

  • Vite for building and bundling
  • Biome for linting and formatting
  • commitlint for conventional commits
  • lefthook for git hooks
  • semantic-release for automated versioning

Please ensure your commits follow the Conventional Commits specification.

License

MIT