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

@leapdev/a11y-sdk

v0.1.0

Published

Framework-agnostic browser accessibility utilities.

Readme

LEAP Accessibility SDK

A framework-agnostic, browser-focused TypeScript package for LEAP accessibility functionality.

Package structure

The SDK exposes each feature through a separate package subpath:

import {} from '@leap/a11y-sdk/font-scaling';
import {} from '@leap/a11y-sdk/font-selection';
import {} from '@leap/a11y-sdk/highlight-focus';
import {} from '@leap/a11y-sdk/reading-guide';

All four feature modules are available for use.

There is deliberately no root entry point. Consumers import only the feature they need, and the package declares that its modules have no side effects so bundlers can remove unused code.

Install from the distributed tarball

The SDK is distributed as a .tgz package. Consumers do not need to clone or build this repository.

Requirements

The consuming application must provide:

  • A modern browser environment.
  • An ESM-compatible bundler.
  • TypeScript when consuming the included declarations.
  • Yarn or npm.

Verify the archive

If a checksum was supplied with the tarball, obtain it through a trusted channel separate from the archive and verify it before installation.

On macOS:

shasum -a 256 ./leap-a11y-sdk.tgz

On Linux:

sha256sum ./leap-a11y-sdk.tgz

On PowerShell:

Get-FileHash .\leap-a11y-sdk.tgz -Algorithm SHA256

Confirm that the result matches the separately supplied checksum.

Install with Yarn

From the consuming application's root directory:

yarn add ./path/to/leap-a11y-sdk.tgz

Install with npm

npm install ./path/to/leap-a11y-sdk.tgz

The package is installed as @leap/a11y-sdk.

Keep the tarball available

The package manager records the archive as a file: dependency. The file must remain available whenever dependencies are installed.

For temporary evaluation, keep the dependency changes local. For committed usage, use a stable path available to developers and CI, such as:

vendor/leap-a11y-sdk.tgz

Install the archive from that location:

yarn add ./vendor/leap-a11y-sdk.tgz

Follow the consuming repository's policy before committing binary package archives.

Verify the installation

Run the consuming application's normal checks after installation. Script names vary by repository; for example:

yarn install
yarn typecheck
yarn build

Confirm that Yarn resolved the package:

yarn why @leap/a11y-sdk

With npm:

npm ls @leap/a11y-sdk

Import a feature

Import the required feature through its package subpath:

import { createFontScaling } from '@leap/a11y-sdk/font-scaling';
import { createFontSelection } from '@leap/a11y-sdk/font-selection';
import { createHighlightFocus } from '@leap/a11y-sdk/highlight-focus';
import { createReadingGuide } from '@leap/a11y-sdk/reading-guide';

The package root does not export feature APIs. This import will not work:

import { createHighlightFocus } from '@leap/a11y-sdk';

Basic usage

import { createHighlightFocus } from '@leap/a11y-sdk/highlight-focus';

const highlightFocus = createHighlightFocus();

highlightFocus.enable();

// Destroy the controller when its owning application or view is torn down.
highlightFocus.destroy();

The SDK does not initialise features automatically. The consuming application is responsible for:

  • Creating controllers in a browser context.
  • Enabling and configuring features.
  • Connecting feature flags and persisted preferences.
  • Passing application-specific selectors and exclusions.
  • Destroying controllers during application, view, or iframe teardown.

Applications using server-side rendering must not create controllers on the server. Create them from a browser-only lifecycle hook after the target document has its required head, body, and defaultView, or after the owning framework view has mounted.

Font scaling, font selection, and highlight focus require a controller for each target iframe document. Parent code can create those controllers only for same-origin iframe documents; otherwise, initialise the SDK from code running inside the iframe.

Reading guide is different: one parent controller renders the host overlay, and iframe pointer movement must be forwarded through the documented postMessage contract. Creating a reading-guide controller inside an iframe renders a separate local guide and does not provide the sender.

Update the tarball

When a new archive is supplied, remove the existing file dependency and install the new archive:

yarn remove @leap/a11y-sdk
yarn add ./path/to/leap-a11y-sdk.tgz

With npm:

npm uninstall @leap/a11y-sdk
npm install ./path/to/leap-a11y-sdk.tgz

Commit the resulting package.json and lockfile changes only when the tarball location is stable and available to CI. CI can then use its normal immutable install command, such as yarn install --immutable or npm ci.

Troubleshooting

Package subpath is not exported

Import a documented feature subpath rather than the package root:

import { createReadingGuide } from '@leap/a11y-sdk/reading-guide';

The SDK is ESM-only and does not support CommonJS require(). If a valid subpath still fails, confirm that the consumer emits ESM and uses a compatible TypeScript module resolution mode such as Bundler, Node16, or NodeNext.

document is undefined

The controller is being created outside a browser context. Move controller creation into the application's browser lifecycle after the target document has the DOM structures required by that feature.

CI cannot find the tarball

The file: dependency points to a path that is unavailable in CI. Move the archive to a stable repository-relative location or distribute it through the approved internal package registry.

An updated tarball behaves like the previous version

Remove and reinstall the dependency so the package manager recalculates the archive checksum:

yarn remove @leap/a11y-sdk
yarn add ./path/to/leap-a11y-sdk.tgz

With npm:

npm uninstall @leap/a11y-sdk
npm install ./path/to/leap-a11y-sdk.tgz

Font scaling

Font scaling reproduces the Sirius inline font-size scaling algorithm while keeping application-specific selectors in the consumer:

import { createFontScaling } from '@leap/a11y-sdk/font-scaling';

const fontScaling = createFontScaling({
  targets: {
    selectors: [
      'a',
      'button',
      'input',
      'select',
      'textarea',
      'label',
      'p',
      'li',
      'h1',
      'h2',
      'h3',
    ],
    excludeSelectors: [
      '#a11y-widget',
      '#a11y-reading-guide',
      '#a11y-live',
      'sc-icon',
      '.x-icon',
      '[class*="icon"]',
    ],
  },
});

fontScaling.setScale(1.25);
fontScaling.getScale();
fontScaling.reset();
fontScaling.destroy();

Scaling defaults to the Sirius bounds of 0.85 through 1.75. Values outside the bounds are clamped, and non-finite values reset the scale to 1. Custom bounds must be finite, positive, ordered, and include 1. Values less than 0.001 away from 1 normalize to 1 and trigger a full reset.

The controller records each matched element's original computed and inline font size before applying originalSize * scale as an inline pixel value. Reset and destroy restore tracked elements even if they have been detached, including their original inline priority, pre-existing tracking attributes, root scale property, and root scale attribute.

The LS2-3312 ancestor compensation is preserved to avoid double scaling inherited font sizes in newly inserted nested content. As in Sirius, explicit-size descendants inside an inserted scaled subtree receive the same compensation and may therefore be under-scaled.

A MutationObserver watches newly added light-DOM content only while scaling is active. Insertions are batched so computed-style reads complete before inline writes. One active controller is allowed per document. Each iframe document requires its own controller, and shadow-root content is not traversed. Only child-list changes are observed; existing elements that start or stop matching because of class or attribute changes are not automatically updated.

destroy() is idempotent and terminal. After destruction, setScale() and reset() throw, while scale and getScale() remain readable and return 1. Selector configuration must be trusted and does not accept :has() or CSS escapes. Each selector list accepts at most 100 entries, 2,048 characters per selector, and 8,192 normalized characters in total.

The consumer owns selectors, exclusions, slider behavior, persistence, legacy zoom migration, feature flags, labels, announcements, and analytics. Layout reflow, clipping, and application style changes made while scaling is active remain consumer concerns.

Highlight focus

Highlight focus reproduces the Sirius focus indicator for links, buttons, [role="button"], inputs, selects, and textareas when they match :focus-visible.

import { createHighlightFocus } from '@leap/a11y-sdk/highlight-focus';

const highlightFocus = createHighlightFocus();

highlightFocus.enable();
highlightFocus.disable();
highlightFocus.destroy();

Controllers begin disabled. enable() and disable() are idempotent, and a disabled controller can be enabled again. destroy() is idempotent and terminal; calling enable() afterward throws. Read highlightFocus.isEnabled to check whether the indicator is currently applied.

React usage

React consumers can align the controller lifecycle with a component effect:

import { useEffect } from 'react';
import { createHighlightFocus } from '@leap/a11y-sdk/highlight-focus';

export const HighlightFocus = (): null => {
  useEffect(() => {
    const highlightFocus = createHighlightFocus();
    highlightFocus.enable();

    return () => {
      highlightFocus.destroy();
    };
  }, []);

  return null;
};

React is not a dependency of the SDK; this is a consumer integration example.

The indicator uses the Sirius visual treatment: a 3px solid #FFD700 outline with a 2px offset. These values and selectors are intentionally fixed so the SDK preserves existing behaviour. The gold outline may not provide 3:1 contrast against light backgrounds.

declare const pageCspNonce: string;

const highlightFocus = createHighlightFocus({
  root: document,
  nonce: pageCspNonce,
});

root accepts a Document or ShadowRoot, and each iframe requires its own controller. A document-level controller cannot style inside shadow roots, and a shadow-root controller cannot style nested shadow trees; create a controller for each required root.

When a nonce is required, it must be generated unpredictably for each response and match the nonce in that response's Content Security Policy header. If the stylesheet is blocked, such as by a missing or mismatched nonce, enable() throws.

Feature flags, widget state, preference persistence, announcements, and analytics remain the responsibility of the consuming application.

Font selection

Font selection provides the Sirius readable, atkinson, and opendyslexic typography presets:

import { createFontSelection } from '@leap/a11y-sdk/font-selection';

const fontSelection = createFontSelection({
  fontLoading: 'cdn',
  targets: {
    variableSelectors: [':root', '[class*="ag-theme-"]'],
    fontFamilyVariables: ['--ag-font-family', '--bs-body-font-family'],
    fontFamilySelectors: ['body', 'button', 'input', 'select', 'textarea'],
    metricsSelectors: ['button', 'input', 'select', 'textarea'],
  },
});

fontSelection.setFont('atkinson');
fontSelection.getFont();
fontSelection.setFont('none');
fontSelection.destroy();

The fixed presets are:

  • readable: Verdana/Geneva, 1.6 line height, 0.05em letter spacing.
  • atkinson: Atkinson Hyperlegible with Segoe UI fallback, 1.5 line height, 0.02em letter spacing.
  • opendyslexic: OpenDyslexic with sans-serif fallback, 1.6 line height, 0.05em letter spacing.

The SDK owns the fixed Sirius preset values and font asset loading. The consuming application supplies its CSS variables and selectors, keeping Sirius-specific component names outside the package.

Font assets load only when Atkinson Hyperlegible or OpenDyslexic is first selected. Font loading defaults to none, so consumers must explicitly choose cdn before the SDK contacts the existing Sirius sources. none and readable make no external requests. Consumers can instead select local loading with their own font paths. Local Atkinson assets must be same-origin WOFF2 files, while local OpenDyslexic assets must be same-origin WOFF files. Missing local paths fall back through the configured font-family stack.

Controllers start with none selected. Calling setFont('none') removes the typography override while retaining loaded font assets. destroy() removes all injected assets and permanently disables the controller. Only one active controller is allowed per document.

Each iframe document requires its own controller. Shadow DOM content is affected only through inherited values or styles configured inside that shadow tree.

The optional nonce applies to injected <style> elements and the Google Fonts stylesheet link. CDN mode also requires Content Security Policy allowances for Google Fonts and jsDelivr. Local font URLs must be same-origin and require a matching font-src policy.

Feature flags, widget state, persistence, labels, announcements, and analytics remain the responsibility of the consuming application.

Reading guide

Reading guide reproduces the Sirius mouse-following overlay:

import { createReadingGuide } from '@leap/a11y-sdk/reading-guide';

const readingGuide = createReadingGuide();

readingGuide.enable();
readingGuide.disable();
readingGuide.destroy();

The guide has a fixed 40px CSS height plus 2px top and bottom borders. Sirius positions it with a -20px transform offset using requestAnimationFrame. It preserves the existing colours, z-index, and pointer-events: none behavior. Tracking uses mousemove only; touch, pointer, and keyboard movement are not handled.

Controllers begin disabled. enable() and disable() are idempotent while the controller is active, destroy() is idempotent and terminal, and calling enable() after destruction throws. isEnabled exposes the current state.

Creation injects the overlay and stylesheet immediately and allows one active controller per document. The optional Document must already have head, body, and defaultView; an optional CSP nonce can be supplied. Creation throws if the stylesheet is blocked.

The host controller preserves the existing Sirius postMessage receiver and accepts movement only from an iframe already present in that document. A controller created inside an iframe renders a local guide but does not send movement to its parent. This ticket does not add the missing iframe sender. Consumers implementing a sender must post this message from the iframe window:

{
  source: 'leap-accessibility-bridge',
  version: 1,
  event: 'readingGuideMove',
  payload: { y: mouseEvent.clientY },
}

Feature flags, widget controls, preference persistence, labels, announcements, analytics, and iframe sender integration remain the responsibility of the consuming application.

Development

yarn install
yarn typecheck
yarn build

Build output is written to dist/ as ESM JavaScript, source maps, and TypeScript declarations.