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

@batthewz/response-ui-react-components

v0.8.2

Published

React component library for the response-ui design system. Pairs with @batthewz/response-ui-css.

Readme

@batthewz/response-ui-react-components

~80 accessibility-first React 19 components you re-skin from ~1 page of CSS — without touching a single component.

Every visual decision — colour, spacing, type, radii, shadows, motion — comes from a framework-agnostic CSS token contract, not from the components. Flip one attribute and the whole app re-skins, at runtime, with no rebuild:

// The whole idea: same components, different theme, zero component edits.
<html data-theme="grimdark">
  {" "}
  {/* try: default · events · tech · grimdark */}
  <Button variant="primary">Continue</Button>
</html>

See it live: ai-website-starter.benmatthews-it.workers.dev/demo — every component, every theme, every responsive scale, in one place. The theme switcher is the whole pitch: same components, one-file theme swaps, live.

Also: zero CSS-in-JS, router-agnostic, headless where it counts, RSC-friendly.

Why this over another React component library?

The headline reason is reskinnability. In most libraries the look is welded to the components — a large JS theme object (MUI, Chakra), per-component source you fork and own forever (shadcn/ui), or a styled-components runtime. Re-skinning means editing components, wrangling a theme config, or a rebuild. Here the look lives outside the components, in a framework-agnostic CSS token contract — and everything below follows from that one decision:

  • Re-skin the entire library from ~1 page of CSS. Override ~30 custom properties — colours, spacing, fonts, radii, shadows, motion timing — and every component re-tunes at once. Flip data-theme and the whole app changes at runtime: no rebuild, no JS theme object, no component edits. Four themes (default, events, grimdark, tech) ship as proof.
  • The same theme re-skins more than React. Because the design language is pure CSS, one theme file restyles your React components and your Astro / Rails / Phoenix / plain-HTML pages alike. The brand is a single source of truth across your whole stack, not duplicated per framework.
  • Responsive tokens, not breakpoint soup. text-h2, gap-r3, p-r4 each carry both breakpoints — and headings/body carry their paired line-height and weight step-ups too. You stop hand-writing sm: variants and leading-*.
  • Zero CSS-in-JS, zero runtime styling cost. Styling is co-located plain CSS that self-registers with Tailwind v4. Nothing computes styles at render time; presentational primitives carry no "use client" and stay server-renderable (RSC-friendly out of the box).
  • Headless where it matters. Router-agnostic links, auth gating that takes a status string, and a Standard-Schema form layer with no validator lock-in — behaviour is decoupled from whichever router / auth / validation library you happen to use.
  • Breadth and correctness. ~80 components — including DataTable (three wiring modes), VirtualizedDataTable, CommandPalette, Wizard — with accessibility and a contrast contract baked in, not bolted on.
  • Lighter on AI tokens. Terse token syntax plus a shipped AGENTS.md mean agents read and generate far less to style a screen.

Want only the design language, no React? It ships standalone as @batthewz/response-ui-css — pure CSS, zero JS, usable from any framework.

Is this for you? Best fit: you want one brand applied consistently across an app — and ideally across non-React stacks too — expressed in tokens (p-r3, bg-surface-1, text-h2); If ever you think your project (or sections of it) will need to be reskinned/rethemed/rebranded at any point; If your product services many clients or stakeholders who want their own flexible styling.

Quick start

bun add @batthewz/response-ui-react-components @batthewz/response-ui-css \
  react react-dom @floating-ui/react lucide-react
bun add -D tailwindcss @tailwindcss/vite

Then two CSS imports in your app's CSS entry — foundation (tokens, themes, responsive scales, animations) first, then per-component styles:

/* src/app.css */
@import "@batthewz/response-ui-css";
@import "@batthewz/response-ui-react-components/styles";

Order matters: each per-component file reads var(--…) tokens defined by response-ui-css, so the foundation has to load first.

The styles import also registers this package's sources with Tailwind v4 (a self-relative @source), so the utility classes used inside the components are generated automatically — no manual @source workaround needed, regardless of package manager or node_modules layout (hoisted npm, bun's isolated store, pnpm).

import { Button, Card, Stack } from "@batthewz/response-ui-react-components";

export function Hello() {
  return (
    <Card>
      <Stack gap="r3">
        <h2 className="text-h3">Hello</h2>
        <Button variant="primary">Continue</Button>
      </Stack>
    </Card>
  );
}

That renders a themed Card. Now set data-theme on your <html> (or any ancestor element) and everything inside re-skins at once — no other change. That's the whole model; full theming below.

Theming & reskinning the whole library

Every component renders with var(--…) tokens defined by response-ui-css — none of them hard-code a colour, size, or font. So reskinning is editing the foundation, never the components. A theme is just one CSS file overriding the documented custom properties under a data-theme selector.

You don't need the useTheme hook — or any library JS — to apply a theme. Switching is just setting a data-theme attribute, so the simplest path is to set it declaratively on the root element:

// a Next.js root layout, your index.html, your top-level App — wherever <html> lives
<html data-theme="grimdark">

Reach for useTheme only when you want a theme switcher: it adds reactive state, localStorage persistence, and SSR-safe hydration on top of that same attribute.

import { useTheme } from "@batthewz/response-ui-react-components";

const { theme, setTheme, themes } = useTheme();
setTheme("grimdark"); // also: "events", "tech", "default"

It's pure convenience over the attribute — document.documentElement.setAttribute("data-theme", "grimdark") does the same thing.

Scope a theme to a subtree. The built-in themes target <html> via a :root[data-theme="…"] selector. Author your own theme with a bare [data-theme="aurora"] selector instead, and you can drop data-theme="aurora" on any element — a single panel, a dark island in a light page — and only that subtree re-skins, because the tokens cascade to its descendants.

Write your own theme: copy the template, override the contract in ~1 page of CSS, @import it after the foundation, then register its name with the hook:

/* src/app.css */
@import "@batthewz/response-ui-css";
@import "./themes/aurora.css"; /* your ~1 page of token overrides */
@import "@batthewz/response-ui-react-components/styles";
const { setTheme } = useTheme({ themes: ["default", "aurora"] as const });
setTheme("aurora");

The full reskinning surface lives in the foundation package. These are the canonical, live docs on GitHub:

  • Theme contract — the authoritative list of every overridable token (colours, spacing, type, radii, shadows, motion) and the contrast contract themes must honour.
  • Extending the foundation — add your own tokens, responsive/theme-aware values, and register sources with Tailwind.
  • Theme template — a blank theme to copy as your starting point.
  • response-ui-css README — the foundation in full: responsive scales, the four built-in themes, subpath exports, and how the token system fits together.

What ships

  • UI (50): Accordion, Alert, AppShell, Avatar (+AvatarGroup), AvatarUpload, Badge, Breadcrumbs, Button, Calendar, RangeCalendar, Card, Carousel, CodeBlock, Collapsible, CommandPalette, ContextMenu, CopyButton, DataTable, Dialog, Drawer, DropdownMenu, EmptyState, ErrorBoundary, FileUpload, Hero, HoverCard, IconButton, Kbd, MasonryGrid, MediaCard, Pagination, Popover, Portal, ProgressBar, Rating, Skeleton, Spinner, Spotlight, StatCard, Stepper, Swimlane, Table, Tabs, Text, ThemeSwitcher, Timeline, Toast (+ToastProvider/useToast), Tooltip, VirtualizedDataTable, Wizard (+useWizard)
  • Form (22): Checkbox, ColorPicker, Combobox, DatePicker, DateRangePicker, Field, FieldError, FormActions, Input, Label, MultiSelect, NumberInput, OTPInput, Radio, RangeSlider, Repeater, SearchInput, Select, Slider, Switch, TagInput, Textarea
  • Form orchestration (headless): useForm, FormProvider, useFormContext, useFieldState, useFormState, useFieldArray — Standard Schema validation, a unified field() accessor, useSyncExternalStore-backed reactivity
  • Data display (5): Sparkline, ProgressRing, Meter, DescriptionList, ActivityFeed
  • Layout (6): Center, Container, Divider, Row, Spacer, Stack
  • Animation (5): AnimatePresence, Parallax, ScrollReveal, Stagger, ViewTransition (+useViewTransition)
  • Guards (1): RequireAuth (headless)
  • Router (1): RouterAdapterProvider, useLink, usePathname
  • Hooks: useActiveSection, useClickOutside, useControllableState, useDebounce, useDocumentTitle, useFloating, useFocusTrap, useMediaQuery, usePrefersReducedMotion, useRovingFocus, useTheme, useVirtualRows
  • Util: cn, createCn, mergeExtension, tailwindMergeExtension, twMerge, mergeRefs, formatBytes, plus date helpers (formatDate, parseDateInput, buildMonthGrid, addDays, addMonths, …)

Router adapter — wire your router once

Components like AppShell.SidebarLink and Breadcrumbs.Item render navigational links. Wrap your app once at the root with the adapter:

import {
  RouterAdapterProvider,
  type RouterLinkComponent,
  type RouterLinkProps,
} from "@batthewz/response-ui-react-components";
import { forwardRef } from "react";
import { BrowserRouter, Link as RRLink, useLocation } from "react-router-dom";

const AdapterLink: RouterLinkComponent = forwardRef<
  HTMLAnchorElement,
  RouterLinkProps
>(function AdapterLink({ to, replace, children, ...rest }, ref) {
  return (
    <RRLink ref={ref} to={to} replace={replace} {...rest}>
      {children}
    </RRLink>
  );
});
const adapter = {
  Link: AdapterLink,
  usePathname: () => useLocation().pathname,
};

export function App() {
  return (
    <BrowserRouter>
      <RouterAdapterProvider value={adapter}>
        {/* your routes */}
      </RouterAdapterProvider>
    </BrowserRouter>
  );
}

If you skip the provider, links fall back to plain <a href> — fine for static / non-SPA.

Auth gating — headless RequireAuth

The package ships a router/auth-agnostic RequireAuth that takes a status string and renders accordingly:

import { RequireAuth } from "@batthewz/response-ui-react-components";
import { Navigate } from "react-router-dom";
import { useSession } from "your-auth-library";

export function AuthGuard({ children }) {
  const { data: session, isPending } = useSession();
  const status = isPending
    ? "loading"
    : session
      ? "authenticated"
      : "unauthenticated";
  return (
    <RequireAuth
      status={status}
      unauthenticatedFallback={<Navigate to="/login" replace />}
    >
      {children}
    </RequireAuth>
  );
}

Forms — headless useForm

A store-backed, dependency-free form layer for the form controls. Validation is via Standard Schema, so bring any conforming validator (Zod, Valibot, ArkType, …) — no runtime dependency is added. A single field(name) accessor binds both native inputs and the library's controlled components (Combobox, TagInput, Slider, Select, …) — no register-vs-Controller split.

import {
  FormProvider,
  Field,
  FieldError,
  Input,
  Label,
  useForm,
} from "@batthewz/response-ui-react-components";
import { z } from "zod";

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  password: z.string().min(8, "At least 8 characters"),
});

export function SignIn() {
  const form = useForm({
    defaultValues: { email: "", password: "" },
    schema,
    mode: "onBlur", // onSubmit | onBlur | onChange | onTouched | all
    onSubmit: async (values, { setError }) => {
      const res = await api.signIn(values);
      if (!res.ok) setError("password", "Wrong email or password"); // server error
    },
  });

  return (
    <FormProvider form={form}>
      <form {...form.props}>
        <Field name="email">
          <Label>Email</Label>
          <Input type="email" {...form.field("email")} />
          <FieldError />
        </Field>
        <Field name="password">
          <Label>Password</Label>
          <Input type="password" {...form.field("password")} />
          <FieldError />
        </Field>
        <Button type="submit">Sign in</Button>
      </form>
    </FormProvider>
  );
}

<Field name="x"> auto-wires that field's error into context; <FieldError /> with no children renders it (with role="alert" + aria-describedby), and the bound input reflects aria-invalid. Manual/server errors set via setError always win and survive a validation pass; schema errors surface only once a field is touched/dirty or the form has been submitted — so errors never flash at a field the user hasn't reached.

For non-string values, annotate the bind: form.field<string[]>("tags"). checked-based controls (Checkbox, Switch) are wired via watch/setValue instead of field():

<Switch
  checked={Boolean(form.watch("subscribe"))}
  onChange={(v) => form.setValue("subscribe", v)}
/>

useFieldArray drives dynamic lists with stable keys (id survives reorders):

const { fields, append, remove } = useFieldArray({ form, name: "items" });
fields.map((item) => (
  <Field key={item.id} name={`${item.name}.label`}>
    <Input {...form.field(`${item.name}.label`)} />
  </Field>
));

The component calling useForm re-renders on any change. For render isolation, useFieldState(form, name) and useFormState(form) subscribe to a single field slice / form-level flags only. Other knobs: reValidateMode, criteriaMode, trigger, reset/resetField, focusFirstError, and a reactive external values prop that re-seeds the form when its identity changes.

Adding custom Tailwind tokens

If you add custom design tokens (e.g. bg-brand-foo), build a project-local cn with createCn so it merges both the built-in tokens and yours:

// app/cn.ts
import { createCn } from "@batthewz/response-ui-react-components";

export const cn = createCn({
  theme: {
    color: ["brand-foo", "brand-accent"],
    spacing: ["xtra-tight"],
  },
});

Then import cn from app/cn everywhere instead of from the package directly. createCn concatenates your arrays onto the built-ins, so customising one key (e.g. color) can't accidentally wipe awareness of the others (spacing, text) — the way a manual spread could. For power users, mergeExtension and the raw frozen tailwindMergeExtension are also exported. See docs/extending.md.

Building your own components

Want to use this library as a base — keep the components you like and add your own that share the design tokens (charts, dashboards, domain widgets)? Because you style new components with the same tokens, they inherit every theme for free: a component built on bg-surface-1 / text-fg-primary / p-r3 re-skins alongside the built-ins when you flip data-theme, with no extra wiring. See docs/extending.md for the build-on-top model, the token-compliant component pattern, custom tokens, and the shipped dashboard vocabulary (bg-chart-*, text-trend-*, Sparkline, Meter, …).

The conventions that keep extensions on-theme and consistent are machine-encoded in AGENTS.md — token-only styling (never raw p-4 / bg-gray-100), cn() composition, forwardRef + semantic HTML, the contrast contract, and a "don'ts" list. Point your AI assistant at it and generated components follow the same patterns the library applies to itself.

DataTable wiring modes

DataTable supports three wiring modes — pick one and stick to it:

  • Client-everything — pass pageSize (and optionally defaultSort). The table sorts, slices, and derives pages entirely on the client from the full data array. No onSortChange / onPageChange needed.
  • Server-controlled — pass sort + onSortChange and page + totalPages + onPageChange, and omit pageSize. The table renders exactly the rows you give it and reports sort/page intent back to you; you do the sorting and paging server-side.
  • Hybrid / server-paged (lazy-load) — never enable uncontrolled sorting here; use controlled sort. Accumulate fetched rows into the data array as the user pages, and render a footer sentinel (via the footer slot) to trigger the next load.

Expandable rows

Pass renderExpanded to give every row a leading expander toggle that reveals a full-width detail panel beneath it (accordion-animated, respects prefers-reduced-motion). Expansion is uncontrolled by default; pass expandedKeys + onExpandedChange to control it. Composes with selectable — the detail cell spans the expander, selection, and data columns.

Large datasets: VirtualizedDataTable

For tens of thousands of rows, reach for VirtualizedDataTable instead of paginating. It shares DataTable's ColumnDef and sorting contract but windows the rows (via the useVirtualRows hook) so only a small visible slice is mounted in the DOM — scrolling stays smooth and memory flat. Pass a fixed rowHeight (cell content must fit it — truncate overflow) and a height for the scroll viewport; the sticky header is on by default. Differences from DataTable: there's no pagination, select-all toggles the entire dataset, and an optional onEndReached callback supports infinite loading (accumulate into data, keep sort controlled when the server sorts).

<VirtualizedDataTable
  data={tenThousandRows}
  columns={columns}
  rowKey={(r) => r.id}
  rowHeight={44}
  height={480}
/>

RSC / Server Components

Interactive modules ship a "use client" directive, so the components work out of the box in React Server Component frameworks (Next.js App Router, etc.). Pure presentational components (Button, Text, the layout primitives) carry no directive and stay server-renderable — you can use them directly in server components.

Security: these are presentational Client Components — as with any client component, never pass server-only secrets as props (props are serialized to the browser). The components themselves access no server state or secrets (enforced by bun run verify:directives).

Subpath imports for tree-shaking

Deep imports are supported, so you can pull in a single component or hook without going through the barrel:

import { Button } from "@batthewz/response-ui-react-components/components/ui/Button";
import { useDebounce } from "@batthewz/response-ui-react-components/hooks/use-debounce";

Importing from the root barrel works too — both resolve to the same tree-shakeable modules.

License

MIT.