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

a11y-scroll-cue

v1.0.0

Published

A progressive-enhancement cue for horizontally overflowing content.

Readme

A11y Scroll Cue

A small, dependency-free progressive enhancement that makes horizontal overflow easier to discover. It shows a visible text cue, dismisses it after real horizontal scrolling, and adds a keyboard tab stop only while the scrollport overflows.

The plugin works with tables, code blocks, card strips, and other semantic content. It is framework-agnostic, ESM-only, and does not initialize on import.

Installation

npm install a11y-scroll-cue
pnpm add a11y-scroll-cue
yarn add a11y-scroll-cue

Usage

import { createA11yScrollCue } from "a11y-scroll-cue";
import "a11y-scroll-cue/styles.css";

const root = document.querySelector("[data-a11y-scroll-cue]");

if (root instanceof HTMLElement) {
  const cue = createA11yScrollCue(root, {
    regionLabel: "Feature comparison table",
  });

  // cue.update();
  // cue.reset();
  // cue.dismiss();
  // cue.destroy();
}

To initialize every matching root:

import { initA11yScrollCueAll } from "a11y-scroll-cue";

const instances = initA11yScrollCueAll();

CSS

Import a11y-scroll-cue/styles.css for the default cue, focus indicator, and state styles. The component exposes these custom properties:

  • --a11y-scroll-cue-background
  • --a11y-scroll-cue-color
  • --a11y-scroll-cue-border-color
  • --a11y-scroll-cue-hint-background
  • --a11y-scroll-cue-hint-color
  • --a11y-scroll-cue-shadow
  • --a11y-scroll-cue-radius
  • --a11y-scroll-cue-z-index

The default transition respects prefers-reduced-motion.

HTML structure

Use a root and an explicit child scrollport. Keeping the generated cue outside the scrollport prevents it from becoming part of the scrollable content.

<div
  class="a11y-scroll-cue"
  data-a11y-scroll-cue
  data-a11y-scroll-cue-message="Scroll horizontally to see all columns"
  data-a11y-scroll-cue-region-label="Feature comparison table"
>
  <div data-a11y-scroll-cue-scrollport>
    <table>
      <!-- Wide semantic table content -->
    </table>
  </div>
</div>

Without JavaScript, the original content remains available and horizontally scrollable when the page's own layout styles provide overflow.

Options

| Option | Data attribute | Default | | --- | --- | --- | | message | data-a11y-scroll-cue-message | "Scroll horizontally" | | threshold | data-a11y-scroll-cue-threshold | 2 | | hideOnScroll | data-a11y-scroll-cue-hide-on-scroll | true | | focusable | data-a11y-scroll-cue-focusable | true | | observeMutations | data-a11y-scroll-cue-observe-mutations | true | | regionLabel | data-a11y-scroll-cue-region-label | "" | | describeScrollport | data-a11y-scroll-cue-describe-scrollport | false |

Invalid dataset booleans and negative or non-numeric thresholds fall back to the defaults.

API

  • createA11yScrollCue(root, options?) enhances one root and returns its existing instance when called repeatedly for the same element.
  • initA11yScrollCueAll(options?, scope?) enhances matching roots in a document or subtree.
  • A11yScrollCue is the plugin-specific class.
  • update() measures overflow and synchronizes visual and semantic state.
  • reset() makes an eligible dismissed cue visible again.
  • dismiss(reason?) hides the cue until reset or until overflow disappears.
  • destroy() removes listeners, observers, generated elements, classes, and plugin-added attributes.

The package also exports frozen DEFAULT_OPTIONS, SELECTORS, CLASSES, ATTRIBUTES, and EVENTS constants.

Events

Lifecycle events dispatch synchronously from the component root. Every event uses bubbles: true, composed: false, and cancelable: false. Every detail includes instance and root.

| Event | Trigger | Additional detail | | --- | --- | --- | | a11y-scroll-cue:init | Instance and observers installed | hasOverflow | | a11y-scroll-cue:update | State reconciliation completed | hasOverflow, visible, dismissed | | a11y-scroll-cue:ready | Initial state applied | hasOverflow, visible, dismissed | | a11y-scroll-cue:error | Required scrollport missing | code, message | | a11y-scroll-cue:overflow-change | Overflow changes after initialization | hasOverflow, previousHasOverflow | | a11y-scroll-cue:dismiss | Cue enters the dismissed state | reason, hasOverflow, visible, dismissed | | a11y-scroll-cue:reset | Reset and its update complete | changed, hasOverflow, visible, dismissed | | a11y-scroll-cue:destroy | Cleanup completes | finalState |

Successful initialization has this guaranteed order:

init → update → ready

The initial measurement establishes the baseline and does not emit overflow-change. Later overflow changes emit overflow-change immediately before update. A reset emits update before reset. destroy is terminal: no later event is emitted, including when a listener destroys the instance during an active event sequence.

Because initialization is synchronous, attach direct or delegated listeners before calling createA11yScrollCue():

import {
  EVENTS,
  createA11yScrollCue,
  type A11yScrollCueEvent,
} from "a11y-scroll-cue";

const root = document.querySelector("[data-a11y-scroll-cue]");

if (root instanceof HTMLElement) {
  root.addEventListener(EVENTS.ready, (event) => {
    const readyEvent = event as A11yScrollCueEvent<typeof EVENTS.ready>;
    console.log(readyEvent.detail.hasOverflow);
  });

  createA11yScrollCue(root);
}

These events are observations, not commands, and cannot cancel plugin behavior. Bubbling events do not cross a Shadow DOM boundary because they are not composed. initA11yScrollCueAll() initializes roots in DOM order and finishes each root's synchronous sequence before starting the next; it does not emit an aggregate event.

Accessibility notes

  • The cue uses visible text, a border, and an arrow instead of color alone.
  • The cue is decorative to assistive technology by default; no live region is created.
  • A scrollport receives tabindex="0" only while horizontal overflow exists and focusable is enabled.
  • When regionLabel is provided, an overflowing focusable scrollport receives role="region" and an accessible name unless it already has one.
  • describeScrollport opt-in links the cue text with aria-describedby.
  • Keyboard scrolling uses the browser's native behavior. The plugin does not override arrow, Page Up, Page Down, Home, or End keys.
  • destroy() restores plugin-added focus and region attributes. Existing author-provided attributes are preserved.

The cue improves overflow discoverability, but it cannot guarantee how every browser, assistive technology, or input method exposes scrolling.

Examples

  • Basic examples compare the cue across a wide table, code sample, and card strip.
  • Accessibility audit queue demonstrates a real-world release workflow with dismissed, reset, status, and opt-in description states.

After installing dependencies, run npm run pages:build and serve docs/ with any local static server.

Docs metadata

Documentation aggregators can import structured metadata without parsing this README:

import { docs } from "a11y-scroll-cue/docs";

GitHub Pages

npm run pages:build rebuilds the package and deterministically regenerates the committed docs/ directory. Do not edit docs/ by hand.

For one-time hosting setup, open repository Settings → Pages, choose Deploy from a branch, then select main and /docs. The expected URL is https://vmitsaras.github.io/A11y-Scroll-Cue/.

Development

npm install
npm run build
npm run typecheck
npm test
npm run pack:check

License

MIT © 2026 Vasileios Mitsaras.