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

@standarx/nav

v0.3.2

Published

Spatial navigation for the web: d-pad, gamepad sticks, TV remotes and arrow keys drive real DOM focus, declared in data attributes.

Readme

standarnav

npm downloads CI core size dependencies license

Spatial navigation for the web: d-pad, gamepad sticks, TV remotes and arrow keys drive real DOM focus, declared in data attributes.

Status: 0.3.2 is on npm since 2026-10-13. The public API is not frozen and the data-snav-* attribute names are not final (ADR-0001); while the major is 0, a minor may break. See ROADMAP.md.

What it does

  • Moves the real DOM focus: the engine calls focus() and reads document.activeElement. No virtual cursor, no shadow focus state (ADR-0005).
  • Plain HTML becomes navigable through attributes; the application writes no navigation code for it.
  • Arrow keys, d-pad buttons, the left stick and TV remote keys produce the same intent, so no move logic branches per device. The right stick scrolls.
  • Containers nest and remember the element last focused in them. A container can wrap in one axis, block a direction, trap every exit, or redirect a direction to a CSS selector; when nothing is found, the engine scrolls one step and rescans once, which is how a virtualised list keeps producing rows.
  • Two modes: composite (arrow keys stay inside composites, a gamepad crosses the page) and app (arrow keys navigate the whole page). onWillMove can veto a move; a candidate the browser refuses to focus hands the move to the next one, and the areas of an image map are candidates, each placed by its shape over its image (ADR-0031); onBoundsHit fires when a direction has nowhere left to go.
  • Zero runtime dependencies, ESM only, and every subpath sits under a size cap CI enforces: 3.38 kB min+gzip for the core, 1.42 kB to add the React adapter next to it, 3.54 kB for the spatial engine, by bun run check:size on 2026-09-26 (ADR-0017). You pay for what you import and nothing else.

Usage

bun add @standarx/nav
import { createInputSystem } from "@standarx/nav";
import { gamepadPlugin } from "@standarx/nav/gamepad";
import { spatialPlugin } from "@standarx/nav/spatial";

const input = createInputSystem({ plugins: [gamepadPlugin(), spatialPlugin({ mode: "app" })] });
// later: input.destroy();

mode: "app" is what makes that snippet move on a keyboard. The default, composite, keeps arrow keys inside the composite that holds the focus, APG-strict, and only Tab crosses between composites; a gamepad has no Tab and crosses the page in either mode. Under app a hover also moves the focus, so a mouse and a pad do not fight over two cursors (ADR-0007). It also takes the arrows of a native radio group or range, unless a scope answers "native" for them (docs/en/navigation.md, ADR-0026).

| Attribute | Placed on | Value | Effect | |---|---|---|---| | data-snav="container" | any element | fixed | Declares a navigation container; moves are scored inside it first. | | data-snav-enter | a container | last | first | nearest | Which element takes focus when a move enters. Default last. | | data-snav-wrap | a container | x | y | both, or bare | Wraps to the opposite edge instead of leaving the container. | | data-snav-block | a container | directions separated by spaces, or bare | Blocks those exits; bare blocks every one. | | data-snav-trap | a container | bare | A move never leaves this container. | | data-snav-scroll | a container | center | Scrolls a newly focused element to the centre instead of nearest. | | data-snav-ignore | any element | bare | Excludes the element from the candidate list. | | data-snav-up / -down / -left / -right | a focusable | a CSS selector | Sends that direction to the first match in the document, before any geometry runs. A match that cannot take the focus, or no match, is ignored and the geometry runs as if the attribute were absent. |

The attributes the engine writes back, for styling, are in docs/en/attributes.md; how a move is decided and what makes an element navigable are in docs/en/navigation.md.

Subpaths

Import only what you use: the root entry pulls in no engine.

| Subpath | What it adds | |---|---| | @standarx/nav | createInputSystem, intent scopes, engage mode for controls that hold a value, the modality tracker, the focusability helpers, the types. | | @standarx/nav/spatial | The engine that turns a direction into a focus move: move, focus, focusFirst, onWillMove, onBoundsHit. | | @standarx/nav/gamepad | navigator.getGamepads polling: standard mapping, dead zones, repeat, rumble. | | @standarx/nav/focus-ring | One overlay that follows focusin, painted inline, six custom properties — docs/en/focus-ring.md. | | @standarx/nav/keyboard | An on-screen keyboard whose keys are real buttons the engine navigates; a layout is data in /keyboard/qwerty, /keyboard/azerty or /keyboard/alphabetic (ADR-0022). | | @standarx/nav/debug | explainMove, the scored candidates of a move; scanNativeSelects, the closed <select>s a television cannot open. | | @standarx/nav/react | NavProvider and the hooks — docs/en/react.md. | | @standarx/nav/vue | NavProvider and the composables, for Vue 3.3 and later — docs/en/vue.md. | | @standarx/nav/svelte | provideNav and the functions around it, for Svelte 5.0 and later — docs/en/svelte.md. | | @standarx/nav/angular | provideNav and the inject* functions, for Angular 20.0 and later — docs/en/angular.md. | | @standarx/nav/auto | autoMount for a page with no framework: waits for the document, and lets data-snav-mode on the root pick the navigation mode — docs/en/auto.md. |

Frameworks

The core is the vanilla API and needs no framework, and @standarx/nav/auto is a start-up helper next to it rather than an adapter (ADR-0023). React, Vue, Svelte and Angular are the adapters shipped today, which completes the order of ADR-0011, all four held to the same parity suite (ADR-0027 for Vue, ADR-0028 for Svelte, ADR-0029 for Angular).

Documentation

  • docs/en/: the attribute tables, how a move is decided and what makes an element navigable, React, Vue, Svelte, Angular, the auto-mount helper, the focus ring. Mirrored file by file in docs/fr/.
  • Playground: the fixture page, driven by keyboard and gamepad. bun run dev serves it from src/, and playground/widgets.ts holds the recipes for a slider, a stepper, a wheel picker, a listbox, a splitter and a native <select>.
  • Browser support: Chromium 85, Safari 15 and Firefox 79 are the declared floor, television runtimes of 2020-2021 are best effort, and no television has been tested (ADR-0013, docs/research/tv-runtime-compatibility.md).
  • docs/specification.md: the problem, the non-goals, the requirements and the gate each public claim has to pass. docs/adr/README.md: every decision with its evidence — package layout ADR-0011, versioning and release ADR-0012.
  • docs/research/competitors.md: eighteen projects compared on real focus, attributes, gamepad and framework independence, with sources.
  • Where it comes from: the engine is extracted from a predecessor implementation, identified in ADR-0002 and nowhere else.

Contributing

CONTRIBUTING.md, in one line: conventional commits, bun never npm, a test with every behaviour change, blocking size budgets, English only, and no claim in a document without the command, date or URL that proves it. CI runs eleven checks on every pull request: lint, typecheck, build with the packaging and size gates, unit tests, a React 18.3 floor, a Vue 3.3.0 floor, a Svelte 5.0.0 floor, an Angular 20.0.0 floor, and the browser suite on chromium, firefox and webkit. Conduct is CODE_OF_CONDUCT.md; a vulnerability goes through SECURITY.md, never a public issue.

License

MIT, copyright Wesley Cormier. See LICENSE.