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

@dloizides/a11y

v1.3.1

Published

The accessibility platform seam for the dloizides.com RN-web UI kit. One authored contract (label/hint/role/state/testID) emitted as platform-correct props: React Native accessibility* on native, real ARIA on web - including the hint, which react-native-w

Readme

@dloizides/a11y

The accessibility platform seam for the dloizides.com RN-web UI kit.

One authored contract in, platform-correct props out — including the hint, which react-native-web otherwise drops silently.

npm install @dloizides/a11y

The bug this exists to fix

All seven portals are React Native + Expo but ship web only. On web, react-native-web maps accessibilityLabelaria-label, but accessibilityHint has no ARIA equivalent and is discarded. An element authored like this:

<TouchableOpacity accessibilityLabel="QR code" accessibilityHint="Opens the QR modal" />

renders aria-label + role + tabindex and no hint attribute at all.

The repo-wide rule "every interactive element needs testID + accessibilityLabel + accessibilityHint" therefore bought nothing on web.

It hid for years because a DOM assertion cannot distinguish a threaded hint from a dropped one — both render identically. No test could see it, so nothing ever failed.


Usage

import { useA11y } from '@dloizides/a11y';

const { a11yProps, hintNode } = useA11y({
  label: 'Save',
  hint: 'Saves the menu and returns to the list',
  role: 'button',
  state: { disabled, busy: loading },
  testID: 'save-button',
});

return (
  <TouchableOpacity {...a11yProps} onPress={onPress}>
    {children}
    {hintNode}
  </TouchableOpacity>
);

hintNode is null on native and when there is no hint, so an unconditional {hintNode} is always correct.

What comes out

| authored | native | web | |---|---|---| | label | accessibilityLabel | aria-label | | hint | accessibilityHint | aria-describedby → a rendered, visually-hidden node | | role | accessibilityRole (RN vocabulary) | role (ARIA vocabulary — see below) | | state | accessibilityState | aria-disabled / -selected / -expanded / -busy / -checked | | testID | testID | testID (RN-web emits data-testid) |


Design notes

Why the hint becomes a node, not an attribute

aria-describedby is a pointer. Without a rendered element carrying the id, the attribute references nothing and assistive technology announces nothing. So the description has to genuinely exist in the document — visually hidden, but present.

The node is rendered as a descendant of the host, so it mounts and unmounts with it; a dangling reference to a node that outlived its owner is not possible. It is hidden with position:absolute + 1×1 + overflow:hidden — never display:none, visibility:hidden or opacity:0, all of which risk removing it from the accessibility tree and silently undoing the entire fix.

Known trade-off: the host's raw textContent now includes the hint

The hidden node is a descendant, so host.textContent returns the label and the hint. This is deliberate and the alternatives are worse (a sibling node needs a wrapper element, which changes flex layout; a detached node can outlive its owner).

What it does not change is the accessible name: aria-label takes precedence over content per the ARIA spec, so getByRole('button', { name }) — which is how every Playwright locator in the fleet targets buttons — is unaffected. There is a test pinning exactly this, because the precedence going missing would be a silent E2E-wide breakage.

The residual exposure is any assertion reading raw textContent off a control that renders a hint. At the time of writing no E2E page object does this on a kit Button (the 20 toHaveText/textContent call sites all target badges, errors, titles and status labels). Prefer getByRole(..., { name }) over text-content matching on interactive elements.

Why it can't be forgotten quietly

Three layers, earliest-catching first:

  1. One call returns both. You cannot obtain a11yProps without hintNode landing in the same destructure, one identifier away. No separate import to discover.
  2. Dev runtime guard. After commit, on web, the hook checks the described-by target is actually in the document and console.errors if not. The failure mode becomes noisy — the one thing the original bug never was.
  3. The dual test gate. Web assertions resolve aria-describedby to real text, so a component that dropped the node fails rather than passing vacuously.

Note who calls this: apps never do. They keep passing accessibilityHint to <Button>. Only the kit components call useA11y, which is a small, reviewed surface.

Why the role table is total

RN's role vocabulary and ARIA's overlap but are not the same set. Pass-through produces invalid ARIA, which browsers ignore — failing the same silent way the hint did. The notable divergences:

| RN | ARIA | why | |---|---|---| | header | heading | header is valid ARIA (a landmark) — pass-through would silently turn every heading into a landmark. Worse than dropping it. | | search | searchbox | ARIA's search is a landmark; RN's means a search field. | | none | presentation | | | image | img | | | adjustable | slider | | | imagebutton, keyboardkey | button | no ARIA equivalent | | text, summary | (omitted) | no faithful equivalent — better to say nothing |

The table lists every A11yRole explicitly and is typed Record<A11yRole, Role>, so adding a role without deciding its ARIA spelling is a compile error.


The dual-platform test gate

npm test runs the suite under two Jest projects with different transforms, environments and module resolution:

| project | react-native resolves to | Platform.OS | renderer | transform | |---|---|---|---|---| | web | react-native-web | 'web' | @testing-library/react → jsdom | ts-jest | | native | the real react-native | 'ios' | react-test-renderer | babel-jest (RN preset) |

npm test           # both
npm run test:web
npm run test:native

Each project asserts what its platform actually emits — the web suite resolves aria-describedby through the DOM to real text; the native suite asserts accessibilityHint === X. The disagreement is the product. If you ever find yourself making the two agree by weakening an assertion, stop.

Both projects also assert their own platform identity (Platform.OS), so a moduleNameMapper leaking into the native project — which would quietly collapse it into a second copy of the web project — fails loudly instead of passing.