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

@devframes/plugin-a11y

v1.2.3

Published

Built-in devframe that audits the user app with axe and highlights accessibility issues in the page.

Readme

@devframes/plugin-a11y

[!WARNING] Experimental This devframe is experimental and may change without a major version bump until it stabilizes.

An accessibility inspector built on devframe. It runs axe-core against a user app and surfaces the violations in a Solid panel:

  • Route-aware tracking: buckets violations by location.pathname and tracks them as you navigate the user app (History-API patched, framework-neutral), persisted in sessionStorage so history survives reloads within a tab session.
  • Dashboard + grouped violations: a Dashboard tab (totals, severity breakdown, per-route inventory, scan controls) and a Violations tab listing every tracked route, grouped, with the active route marked.
  • Select + highlight: hover previews the offending element; ticking a violation's checkbox selects it, giving the card a distinct state and drawing globally-numbered badges over all its elements in the page. <html>/<body> targets get a corner notice instead of a viewport-filling ring.
  • Generate fix prompts: a nav button gathers the selected violations (rule metadata, WCAG tags, docs, and each element's selector/markup/failure summary, grouped by route) into one paste-ready AI prompt in a dialog.
  • Constant scanning: a DOM MutationObserver plus debounced mouse/keyboard/touch rescans, toggleable from the Dashboard.
  • WCAG 2.0–2.2 + best-practice: the broadened axe tag set, with best-practice rules tagged and filterable.
  • Console logging: newly-appeared violations are logged (deduped) to the browser console.

The scan + highlight loop works the same whether the devframe runs as a live dev server or as a baked static build.

How it works

Three pieces, two of them browser-side:

| Piece | Runs in | Role | |-------|---------|------| | Page script (src/inject) | the user app's page | runs axe-core, tracks routes, owns the aggregate state, draws the preview + pinned rings | | Panel (src/spa) | the devtools iframe | Solid SPA: Dashboard + grouped violations, fires preview/pin/rescan | | Node (src/index.ts, src/node, src/rpc) | the node side | get-config RPC (impact taxonomy + runtime config): live in dev, baked in a static build |

The page script and panel talk over devframe's in-page channel (devframe/in-page-channel, contract in src/shared/protocol.ts), not the devframe RPC backend. That is what keeps the live loop working in both modes: neither half needs a server to reach the other, only a same-origin handshake in the same tab. The page script owns the authoritative route → report map as the channel's shared state, which is replayed to late-joining panels and streamed as patches on every change, so the panel stays a pure render of it. devframe RPC carries the data model on top; get-config is a static function, so it resolves over WebSocket in dev and from the baked dump in a static build; the panel forwards its runtime-config slice to the page script over the channel, keeping the page script itself free of any RPC dependency.

devframe deliberately provides no access to the user app's DOM, so the page script is the author-provided bridge into the user app's page. In a hub, the page script is the a11y dock's client script, and the definition declares it by path, so the hub serves it same-origin and devframes: ['@devframes/plugin-a11y'] works with no host wiring. The hub's client runtime (createDevframeClientRuntime from @devframes/hub/client) then imports it into the host page and calls its default export with the client-script context. A host can also serve the module itself (e.g. via /@fs/… under Vite) by attaching a11yClientScriptBundlePath as a per-mount clientScript. Booted that way, the page script also mirrors the active route's scan into the hub's messages feed: a summary entry driven through the loading → idle lifecycle plus one entry per violated rule, carrying the impact-mapped level, WCAG tags as labels, and the first offending element's selector and bounding box (rendered by @devframes/plugin-messages when the hub mounts it). Each entry also carries a navigation action: clicking it in the messages panel activates the a11y dock (hub:docks:activate) deep-linked to the rule + route (or the Dashboard, for the summary). Both minimal hub examples do exactly this. Outside a hub, one <script type="module"> for the same bundle does the job; the demo below shows it (no hub context, so the feed mirror simply stays off).

Configuration

Pass options to createA11yDevframe() (surfaced through get-config, so they reach both the panel and the page script):

createA11yDevframe({
  autoScan: true, //         rescan on debounced interaction (default true)
  logIssues: true, //        log new violations to the console (default true)
  defaultHighlight: false, // auto-pin a route's violations on first scan (default false)
  axe: {
    tags: ['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa', 'best-practice'],
    runOptions: {}, //       extra axe `run` options merged over the defaults
  },
})

Run the demo

The demo serves an intentionally-broken host page and the panel from one origin so they share the channel.

pnpm -C plugins/a11y build       # build the panel + the page-script bundle
pnpm -C plugins/a11y demo        # dev: live WebSocket RPC → http://localhost:4477/

pnpm -C plugins/a11y cli:build   # bake the static deploy (dist/static)
pnpm -C plugins/a11y demo:build  # static: baked RPC dump, no server

Open the URL, then hover any row in the panel, and the matching element in the page gets a focus ring (and scrolls into view if it's off-screen). Both demo modes behave identically; the panel's websocket / static tag is the only tell.

Standalone, without a user app:

pnpx @devframes/plugin-a11y      # the published package, panel only, at /__devframes_plugin_a11y/
pnpm -C plugins/a11y dev         # from source: same, at /__devframes_plugin_a11y/

File map

| Path | Export | Purpose | |------|--------|---------| | src/index.ts | . | createA11yDevframe() (also the default export), declaring the page script as its dock's client script; a11yClientScriptBundlePath is that module, for hosts that serve it themselves | | src/node/index.ts | /node | setupA11y(ctx, options?) registers the RPC functions with the runtime config | | src/cli.ts | /cli | createA11yCli() backs the devframes_plugin_a11y bin | | src/client/index.ts | /client | connectA11y(), a typed browser RPC client wrapper | | src/rpc/ | internal | get-config static RPC + the type-safe client registry | | src/shared/protocol.ts | internal | the page script ↔ panel in-page channel contract (A11yChannelProtocol) | | src/inject/ | internal | the page script (axe scan, highlight overlay, hub messages mirror) → dist/inject/inject.js | | src/spa/ | internal | the Solid panel SPA → assets-pkg/dist (ships in @devframes/plugin-a11y--assets) | | demo/ | internal | same-origin host page + server (dev + static modes) | | tests/ | internal | dev-server RPC + static-build dump |