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

@pyreon/a11y

v0.44.0

Published

Accessibility primitives for Pyreon — screen-reader announcements, visually-hidden content, and stable ARIA ids, with zero setup

Readme

@pyreon/a11y

Accessibility primitives for Pyreon — screen-reader announcements, visually-hidden content, and stable ARIA ids. Zero setup: no provider, no context, no component to mount.

bun add @pyreon/a11y

announce(message, options?)

Speak a message to screen-reader users via an aria-live region. The first call lazily creates a visually-hidden region on document.body and reuses it — works from anywhere, no wiring. No-op on the server.

import { announce } from '@pyreon/a11y'

announce('Settings saved')                              // polite (queued)
announce('Connection lost', { politeness: 'assertive' }) // interrupts — errors only
announce('Copied to clipboard', { clearAfter: 1000 })    // auto-clear after 1s
  • politeness: 'polite' (default — queued, spoken when idle) or 'assertive' (interrupts; reserve for errors / time-critical alerts).
  • clearAfter: ms after which the region is emptied, so stale text isn't re-read.
  • Identical consecutive messages are still re-announced (the region is cleared then re-written on the next frame).

<VisuallyHidden>

Content that's invisible on screen but kept in the accessibility tree (unlike display:none / hidden). For labels and status text sighted users get from context but screen-reader users need spelled out.

import { VisuallyHidden } from '@pyreon/a11y'

<button>
  <SearchIcon />
  <VisuallyHidden>Search</VisuallyHidden>
</button>

Renders a <span> by default; pass as="div" (or any tag) where inline flow is wrong. Other props (id, class, aria-*, …) are forwarded; caller style merges over the clipping base. Don't put focusable controls inside — a visually-hidden focusable element is a keyboard trap.

createA11yId(prefix?)

Stable, SSR-safe unique id for ARIA relationship attributes (aria-labelledby / aria-describedby / aria-controls / for). Wraps @pyreon/core's createUniqueId, so server and client agree — no hydration mismatch.

import { createA11yId } from '@pyreon/a11y'

function Field() {
  const hintId = createA11yId('hint')
  return (
    <>
      <input aria-describedby={hintId} />
      <span id={hintId}>Must be at least 8 characters</span>
    </>
  )
}

Call it inside the component (not at module scope) so each instance gets its own id.

<RouteAnnouncer> / useRouteAnnouncer()@pyreon/a11y/router

Announce client-side route changes to screen-reader users. Single-page navigations change the URL + DOM but fire no page-load event, so assistive tech never announces the new page — this closes that gap. Drop one <RouteAnnouncer> near the router root: it registers a single router afterEach hook that pushes the destination route's meta.title (or "Navigated to <path>") to a polite aria-live region via announce().

import { RouteAnnouncer } from '@pyreon/a11y/router'

function App({ router }) {
  return (
    <RouterProvider router={router}>
      <RouteAnnouncer />
      <RouterView />
    </RouterProvider>
  )
}

Customize the message with format, or opt into assertive / clearAfter / announceInitial:

<RouteAnnouncer format={(to) => `${to.meta.title ?? to.path} page`} />

The hook form useRouteAnnouncer(options?) is equivalent — call it once from a long-lived component.

Imported from the @pyreon/a11y/router subpath (with @pyreon/router as an optional peer dependency), so the base @pyreon/a11y entry stays router-free for consumers who only use announce() / <VisuallyHidden> / createA11yId. SSR-safe — the hook registers only in onMount and announce() no-ops on the server.

License

MIT