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

@pajecawav/ui

v0.1.1

Published

Readme

@pajecawav/ui

React 19 component library: accessible Base UI and Floating UI primitives styled with CSS Modules and design tokens, dark mode via a single html.dark class, and SSR-safe portals.

Install

react and react-dom ^19.0.0 are peer dependencies.

npm install @pajecawav/ui

# or

yarn add @pajecawav/ui

# or

pnpm add @pajecawav/ui

Setup

Import the bundled stylesheet once, in your app entry:

import "@pajecawav/ui/index.css";

or from your own stylesheet:

@import "@pajecawav/ui/index.css";

One file ships everything, in this order:

  1. Reset — the full Tailwind-style reset (@unocss/reset/tailwind-v4.css).
  2. Base page styles — font family, text color, page background and color-scheme set directly on <html>.
  3. Design tokens — the raw scale (--neutral-*, --red-*, --size-*, --font-size-*, --radius-*, --shadow-*, …) and the semantic --ui-* layer (see Theming).
  4. Component styles — every component's styles, scoped as Button__root-<hash> so they never collide with yours.

[!WARNING] This stylesheet restyles your page. The reset plus the base styles on <html> change margins, typography, form controls and the page background of the entire host document — that is what it is for. If your app already has its own baseline CSS, expect conflicts. A reset-free entry point is planned; until it lands, index.css is the only supported entry.

Providers

Wrap your app in UIProvider:

import { UIProvider } from "@pajecawav/ui";

export default function App() {
    return <UIProvider>{children}</UIProvider>;
}

It sets up:

  • a shared tooltip delay group (200 ms open / 100 ms close intent), so tooltips behave consistently and don't stick when moving between targets,
  • icon defaults — every built-in lucide icon inherits the font size of its context,
  • a nested ThemeProvider for dark mode.

Dark mode

The theme contract is one class: dark on <html>. Design tokens, color-scheme and component styles all key off it.

Read and change it with useTheme() (theme, setTheme, toggle, systemTheme), or drive UIProvider/ThemeProvider with a theme prop (e.g. resolved from a cookie on the server) and persist changes through onThemeChange.

In server-rendered apps, apply the theme before first paint with ThemeScript — a blocking inline <script> for the document <head> that toggles the class immediately, preventing a light-to-dark flash (FOUC). A ThemeProvider rendered without a theme prop adopts that pre-hydration class after mount:

import { ThemeScript, UIProvider } from "@pajecawav/ui";

// Root layout, Next.js App Router style. suppressHydrationWarning on <html>:
// the script mutates the class before React hydrates.
export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en" suppressHydrationWarning>
            <head>
                <ThemeScript theme={resolvedTheme} />
            </head>
            <body>
                <UIProvider onThemeChange={persistTheme}>{children}</UIProvider>
            </body>
        </html>
    );
}

Theming

Restyle the whole library by overriding the semantic --ui-* tokens. They reference the raw scale and are re-mapped under html.dark, so one override point per token covers both modes:

:root {
    --ui-surface-page: #f6f7fb;
    --ui-focus-ring-color: #7c3aed;
    --ui-radius-control: var(--radius-full);
}

html.dark {
    --ui-surface-page: #0b0c10;
    --ui-focus-ring-color: #a78bfa;
}

Main token groups (the full list lives in src/styles/tokens.css):

| Group | Tokens | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | Border | --ui-border-width, --ui-border-width-thick, --ui-border-color (+ -strong, -accent, -error, -valid), --ui-separator-thickness | | Surface | --ui-surface-page, -default, -subtle, -muted, -inverse, -error, -valid | | Text | --ui-text-default, -inverse, -muted, -subtle, -placeholder, -error, -valid, -info, -warning, -link | | Focus ring | --ui-focus-ring-width, -thin, --ui-focus-ring-offset, --ui-focus-ring-color | | Radius | --ui-radius-control, --ui-radius-card, --ui-radius-pill | | Control height | --ui-control-height-sm / -md / -lg (28 / 32 / 36 px; 36 / 40 / 44 px on coarse pointers) | | Semantic accent | --ui-accent-positive / -negative / -warning / -info (+ -strong, -indicator, -on, tonal and border steps for Button/Badge) | | Elevation | --ui-shadow-card, --ui-overlay, --ui-overlay-tint | | Stacking | --ui-z-modal < --ui-z-popover < --ui-z-tooltip < --ui-z-toast | | Motion | --duration-short / -medium / -long (+ -safe variants that collapse under prefers-reduced-motion) |

[!NOTE] Border line tokens are HiDPI-aware: at min-resolution: 2dppx they swap to hairline values so borders paint as exactly one physical pixel. Use the tokens instead of hardcoding 1px.

Design system

The kit's visual rules are consumer-facing, not internal: a neutral primary, semantic-only color, one control-height grid (28/32/36px, stepped up to 36/40/44px on touch), flat-by-default elevation, and a written exceptions policy (Switch, InputOTP slots, input-group addons). Build your own controls on the same tokens so rows stay aligned and themes stay coherent — see docs/design-system.md for the named rules and the full policy.

SSR

The library server-renders without hydration mismatches. The contracts worth knowing as a consumer:

  • Portals render nothing on the server. Modal, ModalCard, Alert, Menu, MobileMenu, Tooltip and Toaster suppress their portal subtrees during SSR — even when open — so only in-page parts (e.g. Menu.Trigger) appear in the HTML. Popup content mounts after hydration. Don't assert on popup markup in your own SSR tests.
  • Avatar renders its fallback; loading the image is client-only.
  • Image renders the plain <img> (native semantics, no layout shift); the error fallback appears after hydration.
  • NavigationProgress renders nothing while idle.
  • Textarea renders plain on the server; autosize applies after hydration.
  • ThemeScript is the deliberate exception: it must render its inline script in the server HTML (see Dark mode).

Components

Everything is exported from the package root:

import { Button, Menu, Tabs } from "@pajecawav/ui";

Actions

  • Button — action button with variants, sizes and modes.
  • IconButton — square, icon-only button.
  • ButtonLayout — rows of mixed buttons/controls with pixel-aligned heights.
  • CloseButton — ready-made ✕ button for dismissing modals and banners.
  • Clickable — polymorphic clickable primitive; resolves its root element from href, Component or render.
  • Toggle — two-state (pressed/unpressed) button.
  • ToggleGroup — a group of toggles sharing pressed state.

Forms

  • FormItem — field wrapper providing label, status and disabled context to nested controls.
  • Input — text input.
  • InputGroup — composable input row with before/after addons.
  • Textarea — multiline input that autosizes.
  • InputPassword — password field with a visibility toggle.
  • InputOTP — one-time-password input of character slots.
  • InputDate — segmented date field (dd.mm.yyyy) with a calendar popup.
  • InputDateRange — segmented date-range field.
  • NumberField — numeric input with increment/decrement steppers.
  • Checkbox, Radio, RadioGroup, Switch, Slider — standard controls styled on the token system.
  • Select — custom select with a listbox popup; falls back to a native <select> on touch devices.
  • NativeSelect — styled native <select>.
  • Search — search input with a clear button.
  • Calendar — month-grid date picker (also powers InputDate).
  • Autocomplete — input with a suggestion popup (see Async items).
  • Combobox — single- or multi-select combobox with chips.

Overlays

  • Modal — centered dialog with focus trap and backdrop.
  • ModalCard — dialog that renders its content in a card.
  • Alert — compact alert dialog for confirmations.
  • Menu — dropdown menu: items, links, checkbox/radio items, groups, submenus.
  • MobileMenu — drawer/bottom-sheet menu for mobile viewports.
  • Tooltip — hover/focus hint positioned with Floating UI.
  • Toaster — toast notifications; render <Toaster /> once and fire toasts through the toast manager.
  • Command — command-palette input over a list of actions (the ⌘K pattern).

Navigation

  • Link — hyperlink.
  • Breadcrumbs — breadcrumb trail with separators.
  • Pagination — page navigation.
  • Tabs — tab list with panels.
  • Sidebar — collapsible app sidebar (Sidebar.Provider, Sidebar.Trigger).
  • NavigationProgress — top-of-page progress bar driven by isPending.

Typography

  • Title — heading text.
  • Text — body text.
  • Paragraph — paragraph block.
  • Footnote — small caption text in three weights.
  • ClampText — text clamped to a max number of lines.
  • Kbd — keyboard key cap.
  • Mark — highlighted text fragment.
  • Marker — vertical accent marker for headers and list items.

Layout

  • Card — grouped content container.
  • Cell — list row with before/after slots.
  • Flex — flexbox layout primitive.
  • Separator — horizontal or vertical divider.
  • AspectRatio — constrains children to a ratio.
  • Placeholder — empty-state block (icon, title, action).

Feedback

  • Banner — inline alert banner with icon, title and actions.
  • Progress — progress bar, determinate or indeterminate.
  • Spinner — loading spinner.
  • Skeleton — shimmering loading placeholder.
  • Status — colored status dot with a label (info/positive/warning/negative).

Media & utility

  • Avatar — user avatar with an automatic image → initials fallback.
  • Image — <img> with border, placeholder and error fallback.
  • VisuallyHidden — content exposed to screen readers only.

Compound components

Many components expose their parts via dot notation only — Menu.Item, Tabs.Tab, Banner.Icon — the bare parts are not exported:

<Menu>
    <Menu.Trigger>Actions</Menu.Trigger>
    <Menu.Item>Rename…</Menu.Item>
    <Menu.Separator />
    <Menu.Item disabled>Delete</Menu.Item>
</Menu>

Typed families

Command, Autocomplete and Combobox accept item values of any type, but JSX children can't inherit those type parameters from the parent component. Create a typed family once at module scope instead:

import { createAutocomplete } from "@pajecawav/ui";

interface Country {
    code: string;
    label: string;
}

const CountryAutocomplete = createAutocomplete<Country>();
// createCommand<Item, Group>() and createCombobox<Value, Multiple>() follow
// the same pattern.

<CountryAutocomplete items={countries} itemToStringValue={c => c.label}>
    <CountryAutocomplete.Input placeholder="Country" />
    <CountryAutocomplete.Content>
        <CountryAutocomplete.List>
            {country => (
                <CountryAutocomplete.Item key={country.code} value={country}>
                    {country.label}
                </CountryAutocomplete.Item>
            )}
        </CountryAutocomplete.List>
    </CountryAutocomplete.Content>
</CountryAutocomplete>;

The bare Autocomplete/Command/Combobox exports remain for untyped, simple usage.

Async items (Autocomplete & Combobox)

There is no first-class async API yet (it's on the backlog) — async data is a usage pattern over the existing controlled props:

  • control the input with value / onValueChange,
  • set mode="none" so the component stops filtering client-side — you feed it server-filtered items,
  • announce state through the Status live region (keep it mounted, swap its children) and hide Empty while a request is in flight,
  • guard the race yourself: keep a request id in a ref, bump it on every query, and drop responses whose id is no longer current.

The full pattern — debounce, stale-response guard, loading feedback — is spelled out in the Autocomplete → Async Storybook story.

Helpers

import { cn, mergeProps, useMergeRefs, useRender } from "@pajecawav/ui";
  • cn — class-name merger, re-exported from @pajecawav/utils.
  • useMergeRefs — re-export of the @floating-ui/react hook.
  • mergeProps, useRender — re-exports from @base-ui/react.

These are deliberate convenience re-exports, not reimplementations: the library is built on those exact packages, so custom components composed from the same primitives don't need to add or version-pin the dependencies themselves.

Also exported: the FormFieldStatus ("default" | "error" | "valid") type.

Development

pnpm install
pnpm --filter @pajecawav/ui dev    # Storybook + token/CSS-module watchers