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

@ulam/taho

v0.3.2

Published

ARIA live region announcer. Vanilla core with React, Remix, Vue, and Angular adapters.

Readme

@ulam/taho

ARIA live region announcer. Vanilla core with React, Remix, Vue, and Angular adapters.

Named for taho, the Filipino street drink: warm, light, and always there when you need it.

Purpose & Scope

What taho does:

  • ARIA live regions for screen reader announcements
  • Automatic duplicate message re-announcement (clear-then-set cycle)
  • Route change announcements (when navigation occurs)
  • Accessibility-first design for dynamic content
  • Framework-agnostic vanilla core with framework adapters

What taho doesn't do:

  • UI rendering (announcer is invisible)
  • Message queuing or filtering (you control what gets announced)
  • Timed dismissal beyond announcer's default (use with other patterns)
  • Focus management (use @ulam/sili for that)
  • State management (announcements are side effects only)

Who should use taho:

  • React, Remix, Vue, or Angular apps with dynamic content
  • Projects announcing form validation errors, async results, or status changes
  • Accessibility-first applications requiring screen reader support
  • SPAs that announce navigation changes
  • Any app where users need to know about content changes they can't see

The ulam Framework

Taho is one of six independent packages in the ulam framework. See docs/ARCHITECTURE.md for the complete framework structure and dependency graph.

Install

npm install @ulam/taho

Usage

Vanilla

No setup required. The live region DOM nodes are created lazily on first call.

import { announce } from '@ulam/taho'

announce('Settings saved')
announce('Invalid key', { priority: 'assertive' })

React

import { Announcer, announce, useAnnounce } from '@ulam/taho/react'

// Mount once at app root
<Announcer />

// From anywhere in the app
announce('Copy: copied to clipboard')

// Or via hook inside a component
const announce = useAnnounce()
announce('Search: 12 results')

Remix

For vanilla route announcer (any framework):

import { Announcer, mountRouteAnnouncer } from '@ulam/taho/remix'

const unmount = mountRouteAnnouncer()

For React routes in Remix:

import { Announcer, useRouteAnnouncer } from '@ulam/taho/remix/react'

// In root.jsx
export default function Root() {
  useRouteAnnouncer()
  return (
    <>
      <Announcer />
      <Outlet />
    </>
  )
}

Vanilla route announcer

For any router that fires browser navigation events:

import { mountRouteAnnouncer } from '@ulam/taho/remix'

const unmount = mountRouteAnnouncer()

// Custom label resolver (recommended):
const unmount = mountRouteAnnouncer(
  () => document.querySelector('h1')?.textContent ?? document.title
)

unmount() // clean up on teardown

For routers that do not fire browser navigation events:

import { notifyRouteChange } from '@ulam/taho/remix'

router.on('navigate', ({ pathname }) => notifyRouteChange(pathname))

Why Remix needs this

Remix and React Router v7 do not announce route changes to screen readers. Their official docs acknowledge that "screen-reader users benefit from announcements when a route has changed" but provide no built-in solution, noting only that they are "actively investigating" the problem. That investigation has not shipped.

Without an explicit announcement, screen reader users have no signal that navigation occurred. They can be left reading stale content with no indication the page changed.

SvelteKit ships a built-in route announcer. Nuxt added one in v4.4. React Router does not have one.

The pattern here follows the research-backed guidance from Marcy Sutton's 2019 user testing with disabled users: announce the new page to a live region after navigation settles, using the page heading or document title as the announcement string.

Pair with @ulam/sili/remix for complete coverage. Taho handles the screen reader announcement; sili handles moving keyboard focus to the new content.

Vue

The vanilla announce() function works in Vue without any adapter. Call it directly from <script setup> or composables. The Vue adapter provides a composable for consistency with Vue's Composition API style.

import { useAnnounce } from '@ulam/taho/vue'

// Inside setup()
const announce = useAnnounce()
announce('Settings: Saved')

// Or import vanilla directly (both are equivalent)
import { announce } from '@ulam/taho'
announce('Settings: Saved')

useAnnounce() returns the same vanilla announce function. There is no reactivity overhead. The composable exists purely so Vue developers can use a consistent use* import style.

Angular

The Angular adapter provides an injectable AnnounceService wrapping the vanilla core.

import { AnnounceService } from '@ulam/taho/angular'

@Component({ ... })
export class SettingsComponent {
  constructor(private announcer: AnnounceService) {}

  save() {
    // ... save logic
    this.announcer.announce('Settings: Saved')
  }
}

AnnounceService is providedIn: 'root'. No module or explicit provider needed. Import it and inject it anywhere.

For Angular 14+ standalone apps, you can also call provideAnnounce() explicitly in bootstrapApplication(), though this is optional since the service is already root-provided.

Subpath exports

| Import | Contents | | ------ | -------- | | @ulam/taho | Vanilla core: announce, clearAnnouncements | | @ulam/taho/react | Announcer, useAnnounce, vanilla re-exports | | @ulam/taho/remix | useRouteAnnouncer, mountRouteAnnouncer, notifyRouteChange, React re-exports | | @ulam/taho/vue | useAnnounce, vanilla re-exports | | @ulam/taho/angular | AnnounceService, provideAnnounce |

See the root README for a complete framework support overview across all ulam packages.

Message format

Prefix with context: "Settings: Saved" not "Saved". Bare messages are ambiguous to screen reader users who may have missed where the action came from.

Priority

  • 'polite' (default): waits for a natural pause. Use for confirmations, results, background updates.
  • 'assertive': interrupts immediately. Use only for errors and urgent alerts.

What not to announce

Do not announce focus-managed transitions like modals opening or page navigation. Screen readers announce focus targets automatically. Only announce things that happen outside the user's current focus.

Implementation

Two always-in-DOM live regions with auto-clearing after ~1 second. Duplicate messages re-announce reliably via a clear-then-set cycle. Adapted from @react-aria/live-announcer (Adobe, Apache-2.0).