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

@asanka-npm/a11y-gate

v0.1.1

Published

> WCAG 2.1 Level AA compliance for legacy React/Next.js applications — without modifying internal component states.

Readme

A11y Gate

WCAG 2.1 Level AA compliance for legacy React/Next.js applications — without modifying internal component states.

A11y Gate is a pnpm monorepo containing two packages:

  • @a11y-gate/cli — dev dependency. AST scanner + interactive config generator.
  • @a11y-gate/core — runtime dependency. React Provider + Shield wrapper components.

For a full breakdown of which WCAG 2.1 criteria are currently supported, partially supported, or pending, see ACCESSIBILITY.md.


Architecture

flowchart TD
    subgraph devTime [Dev Time]
        A["npx a11y-gate scan"] -->|"TS-Morph AST"| B["raw-metadata.json"]
        B -->|"Developer annotates intent\n(LLM via IDE or manual)"| C["proposed-manifest.json"]
        C -->|"npx a11y-gate review"| D["a11y-gate.config.json"]
    end

    subgraph runtime [Runtime]
        D -->|"imported at app root"| E["A11yGateProvider config={...}"]
        E --> F["Context + Escape listener + aria-hidden manager"]
        G["Shield component='LegacyDrawer'"] -->|"reads config from context"| F
        G -->|"cloneElement + prop spy"| H["LegacyDrawer isOpen={...} onClose={...}"]
    end

Monorepo Structure

/a11y-gate
├── packages/
│   ├── cli/                     # @a11y-gate/cli (devDependency)
│   │   ├── src/
│   │   │   ├── scan.ts          # TS-Morph scanner → raw-metadata.json
│   │   │   └── review.ts        # Commander.js interactive review → a11y-gate.config.json
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── core/                    # @a11y-gate/core (runtime dependency)
│       ├── src/
│       │   ├── Provider.tsx     # A11yGateProvider (app root wrapper)
│       │   ├── Shield.tsx       # Shield HOC (per-component wrapper)
│       │   ├── types.ts         # A11yGateConfig, ComponentManifest interfaces
│       │   └── utils/
│       │       ├── trap.ts              # Sentinel-node focus trap
│       │       ├── inert.ts             # aria-hidden + inert background management
│       │       ├── tab-order.ts         # Tab-order patching (tabindex, role, aria-label)
│       │       ├── tab-order-preview.ts # Dev-mode numbered tab-order badges
│       │       ├── css-inject.ts        # Scoped CSS overrides (WCAG 1.4.4, 1.4.12)
│       │       └── contrast.ts          # Per-element contrast enforcement (WCAG 1.4.3)
│       ├── package.json
│       └── tsconfig.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
├── package.json
└── a11y-gate.config.json        # Source of truth (human-verified)

Config Schema

raw-metadata.json — CLI output, developer/LLM input

[
  {
    "componentName": "LegacyDrawer",
    "path": "src/components/layout/Drawer.tsx",
    "props": ["isOpen", "title", "onClose", "width"]
  }
]

a11y-gate.config.json — human-verified source of truth

{
  "version": "1",
  "tab_order_preview": true,
  "tab_order_preview_production": false,
  "tab_order_preview_scope": "both",
  "components": [
    {
      "componentName": "LegacyDrawer",
      "componentType": "side-tray",
      "triggerProp": "isOpen",
      "closeHandler": "onClose",
      "rootSelector": "#root",
      "tabOrder": {
        "auto": true,
        "patches": [
          {
            "selector": "[data-action='close']",
            "role": "button",
            "label": "Close"
          }
        ]
      }
    }
  ]
}

Valid componentType values: "modal", "side-tray", "tooltip", "popover".

tab_order_preview shows numbered tab-order tooltips in development. It is disabled in production unless tab_order_preview_production is also true.

tab_order_preview_scope controls where badges appear:

  • "overlay" (default): only inside active Shield overlays
  • "global": across the whole page
  • "both": whole page plus active overlays

tabOrder.auto conservatively patches legacy interactive elements by adding tabIndex=0 in DOM order. tabOrder.patches lets you explicitly target legacy elements that need keyboard reachability. A11y Gate does not use positive tabindex values.


Consumer API

// main.tsx / _app.tsx — app root
import config from './a11y-gate.config.json';
import { A11yGateProvider } from '@a11y-gate/core';

<A11yGateProvider config={config}>
  <App />
</A11yGateProvider>
// At each legacy component call site — per-component wrapper
import { Shield } from '@a11y-gate/core';

<Shield component="LegacyDrawer">
  <LegacyDrawer isOpen={open} onClose={close} title="Settings" />
</Shield>

Integration

The CLI can create a11y-gate.config.json inside a separate or legacy project. Run the commands from the target project root.

1. Install Packages

For local .tgz testing:

npm install --save <local-path>/a11y-gate/packages/core/a11y-gate-core-0.1.0.tgz
npm install --save-dev <local-path>/a11y-gate/packages/cli/a11y-gate-cli-0.1.0.tgz

After publishing to npm:

npm install @a11y-gate/core
npm install -D @a11y-gate/cli

2. Scan The Project

npx a11y-gate scan --src components

Use the directory where the legacy React components live, such as src, app, or components. This creates raw-metadata.json.

3. Generate Config

npx a11y-gate review

This reads raw-metadata.json, asks which components should be managed, and writes a11y-gate.config.json in the target project.

4. Add The Root Provider

In a Next.js App Router project, create a client wrapper:

"use client"

import { A11yGateProvider } from "@a11y-gate/core"
import config from "@/a11y-gate.config.json"
import type { ReactNode } from "react"

export function A11yGate({ children }: { children: ReactNode }) {
  return <A11yGateProvider config={config}>{children}</A11yGateProvider>
}

Then wrap the app in app/layout.tsx:

<A11yGate>
  {children}
</A11yGate>

5. Wrap Legacy Overlays

import { Shield } from "@a11y-gate/core"

<Shield component="DowngradePopup">
  <DowngradePopup
    isOpen={showDowngradePopup}
    onClose={handleDowngradeClose}
  />
</Shield>

The component value must match componentName in a11y-gate.config.json.

6. Enable Tab-Order Preview

{
  "version": "1",
  "tab_order_preview": true,
  "tab_order_preview_production": false,
  "tab_order_preview_scope": "both",
  "components": []
}

Scopes:

  • "overlay": show badges only in active Shield overlays
  • "global": show badges across the whole page
  • "both": show global page badges and overlay badges

7. Run And Verify

npm run dev

Verify that numbered tab-order badges appear in development, wrapped dialogs trap focus, Escape closes wrapped overlays, and background content receives aria-hidden + inert while overlays are open.


Tooling

| Concern | Tool | |---|---| | Monorepo | pnpm workspaces | | CLI bundler | tsup (CJS for Node) | | Core bundler | tsup (ESM + CJS, <5kb gzipped) | | AST parsing | ts-morph | | CLI framework | commander.js | | TypeScript | shared tsconfig.base.json | | Versioning | Changesets |


Key Implementation Details

  • Shield.tsx uses React.cloneElement to spy on triggerProp; when true, activates focus trap and registers its closeHandler callback with Provider context
  • trap.ts injects two invisible sentinel <span tabIndex={0}> nodes around the child subtree; cycles focus on Tab/Shift+Tab. Uses useId() for ARIA IDs to prevent Next.js hydration mismatches
  • tab-order.ts can patch legacy interactive elements with missing tab stops by adding reversible tabIndex=0 attributes before focus trapping runs
  • tab-order-preview.ts renders numbered development-only tooltips that show the effective tab order while an overlay is open
  • inert.ts applies aria-hidden="true" + inert to the rootSelector element when any overlay is active; removes both on deactivation
  • css-inject.ts injects a scoped <style> tag targeting [data-a11y-gate="ComponentName"] * with !important overrides for minimum font size (WCAG 1.4.4) and text spacing (WCAG 1.4.12); cleaned up on overlay close
  • contrast.ts traverses visible text-bearing elements at overlay-open time, resolves effective background by walking the DOM upward past transparent ancestors, and applies the minimum inline color adjustment needed to reach the WCAG 1.4.3 contrast ratio target via binary search; restores originals on close
  • Provider.tsx manages a stack of active overlays; global keydown listener maps Escape to the top-of-stack registered closeHandler callback
  • LLM integration is intentionally left to the developer/IDE — the CLI is LLM-agnostic
  • The library is read-only with respect to the legacy project — it never modifies source files

Build Phases

Phase 1 — Monorepo Scaffolding

  • Init pnpm workspace, root package.json, pnpm-workspace.yaml, tsconfig.base.json

Phase 2 — CLI Package (@a11y-gate/cli)

  • scan.ts: walk src/, identify React components (functional + class), extract prop interface keys → raw-metadata.json
  • review.ts: interactive CLI to annotate raw metadata → a11y-gate.config.json

Phase 3 — Core Package (@a11y-gate/core)

  • types.ts: A11yGateConfig, ComponentManifest TypeScript interfaces
  • Provider.tsx: context, overlay stack, global Escape handler, Shield registration API
  • Shield.tsx: config lookup by name, prop spy via cloneElement, activates trap + inert on trigger
  • utils/trap.ts: sentinel-node focus trap with useId()
  • utils/inert.ts: background inertness management

Phase 4 — Build & Package

  • Configure tsup for both packages
  • peerDependencies for React in core/package.json
  • exports map, files field, publishConfig for npm publishing
  • Changesets for monorepo version management
  • Verify core bundle size target (<5kb gzipped)
  • README with install + usage instructions for both packages