ulam

May 23, 2026 · View on GitHub

Accessibility utilities for the modern web. Vanilla-first, with optional React, Remix, Vue, and Angular adapters.

Named for the Filipino word for dish: the thing everything else is built around.

Accessibility utilities for the modern web. Focus management, live region announcements, i18n, and UI components, each independently installable, vanilla-first, with optional React, Remix, Vue, and Angular adapters. Named for the Filipino word for dish.

Packages

PackageDescription
@ulam/tahoARIA live region announcer, route announcer
@ulam/siliFocus management, overlays, routing hooks
@ulam/calamansiData-agnostic i18n, locale hooks, logic utilities
@ulam/halohaloAI provider adapters, model config, agentic mode
@ulam/ubeFramework-agnostic UI components (vanilla, React, Vue, Angular, Remix), theming, design tokens
@ulam/sawsawanThe sauce: integration layer wiring the above together

Architecture

Each package is independently installable. Dependency flow is strictly one direction:

calamansi ──┐
taho ────────┤──► sawsawan
sili ────────┤
ube ─────────┘

None of the four core packages import from each other or from sawsawan. Sawsawan is the only package that imports from the others.

Install

Most packages are vanilla-first with optional framework adapters. Install only what you need:

npm install @ulam/taho           # vanilla announcer; React, Remix, Vue, Angular adapters optional
npm install @ulam/sili           # vanilla focus management; React, Remix, Vue, Angular adapters optional
npm install @ulam/calamansi      # vanilla i18n; React, Vue, Angular adapters optional
npm install @ulam/halohalo       # vanilla AI adapters; React, Vue, Angular adapters optional
npm install @ulam/ube            # vanilla web components; React, Remix, Vue, Angular adapters optional

Or with npm aliases if you prefer shorter import names:

npm install taho@npm:@ulam/taho
npm install sili@npm:@ulam/sili
npm install calamansi@npm:@ulam/calamansi

Framework support

Packages with framework-specific behavior ship subpath exports:

SubpathFrameworkDescription
@ulam/tahoanyVanilla core
@ulam/taho/reactReactuseAnnounce, Announcer
@ulam/taho/remixRemixRoute announcer, React re-exports
@ulam/taho/vueVue 3useAnnounce composable
@ulam/taho/angularAngular 17+AnnounceService
@ulam/silianyVanilla core
@ulam/sili/reactReactHooks, overlay components, hash router
@ulam/sili/remixRemixSame hooks, Remix router
@ulam/sili/vueVue 3Composables matching all React hooks
@ulam/sili/angularAngular 17+Services and standalone directives
@ulam/calamansianyVanilla core
@ulam/calamansi/reactReactI18nProvider, useT, usePref
@ulam/calamansi/vueVue 3useT, usePref composables
@ulam/calamansi/angularAngular 17+I18nService, PrefService
@ulam/halohaloanyVanilla core
@ulam/halohalo/reactReactuseCompletion, useProviderConfig
@ulam/halohalo/vueVue 3useCompletion, useProviderConfig composables
@ulam/halohalo/angularAngular 17+CompletionService, ProviderConfigService
@ulam/ubeanyVanilla web components
@ulam/ube/coreanyWeb component registrations
@ulam/ube/reactReactReact component adapters
@ulam/ube/remixRemixReact re-exports (Remix is React-based)
@ulam/ube/vueVue 3Vue component adapters
@ulam/ube/angularAngular 17+Angular component decorators, UbeModule

Quick Start by Framework

React

import '@ulam/ube/base-tokens.css'
import '@ulam/ube/base-typography.css'
import '@ulam/ube/ui.css'
import '@ulam/sili/base.css'

import { Router } from '@ulam/sili/react'
import { Announcer } from '@ulam/taho/react'
import { I18nProvider } from '@ulam/calamansi/react'
import { Button, Dialog } from '@ulam/ube/react'

function App() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <I18nProvider>
      <Router>
        <Announcer />
        <Button onClick={() => setIsOpen(true)}>Open</Button>
        <Dialog open={isOpen} onClose={() => setIsOpen(false)} heading="Title">
          Content
        </Dialog>
      </Router>
    </I18nProvider>
  )
}

Remix

For framework-agnostic focus management and vanilla utilities:

import { mountRouteFocus, focusPageHeading } from '@ulam/sili/remix'
import { announce } from '@ulam/taho/remix'

For React routes in Remix, use the /react subexports:

import { useRouter, useRouteMatch } from '@ulam/sili/remix/react'
import { useRouteAnnouncer } from '@ulam/taho/remix/react'
import { Button } from '@ulam/ube/react'

Web components work identically across all frameworks (@ulam/ube/remix re-exports from core).

Vue

Use /vue subpaths:

import { useFocusTrap, useDir } from '@ulam/sili/vue'
import { useT } from '@ulam/calamansi/vue'
import { useAnnounce } from '@ulam/taho/vue'
import { Button } from '@ulam/ube/vue'

Angular

Use /angular subpaths:

import { FocusTrapDirective } from '@ulam/sili/angular'
import { I18nService } from '@ulam/calamansi/angular'
import { AnnounceService } from '@ulam/taho/angular'
import { UbeModule } from '@ulam/ube/angular'

@NgModule({
  imports: [UbeModule],
})
export class AppModule {}

Core Concepts

  • Vanilla-first: Every package has a vanilla core with zero dependencies. Framework adapters are optional add-ons.
  • Independent: Install only what you need. Packages don't import each other.
  • Accessible by default: All components handle focus, keyboard, ARIA, and screen reader support automatically.
  • Strictly Linted: The codebase enforces accessibility and inclusivity through @a11yfred/neighbor.
  • Tree-shakeable: Component CSS imports only what's used. Unused code doesn't bundle.

Resources

License

MIT