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

@ubuligan/testapp

v0.1.0

Published

A publishable React UI kit — shadcn/Radix + rc components, token-driven theming, per-component subpath exports.

Readme

UI Kit

A publishable React component library. Restyle it, publish it to npm or a private registry, and your teammates import components one at a time.

pnpm install
pnpm dev          # Storybook → http://localhost:6006

npm works just as well — npm install, then npm run dev. Every script in this README has an npm run <script> equivalent, and pnpm release shells out to npm internally, so publishing never needs pnpm installed.

Install scripts are allowlisted. Both modern package managers refuse to run a dependency's install script unless you approve it, and esbuild — the engine behind tsup, Vite and Vitest — needs its postinstall to place the platform binary. Under pnpm, skipping it makes the build fail; under npm you get a warning. So esbuild and @parcel/watcher are pre-approved in package.json, once per manager:

"allowScripts": { "esbuild": true, "@parcel/watcher": true },   // npm 11+
"pnpm": { "onlyBuiltDependencies": ["esbuild", "@parcel/watcher"] }  // pnpm 10+

If you add a dependency that ships an install/postinstall script, add it to both listsnpm approve-scripts <pkg> and pnpm approve-builds write them for you. Otherwise the install exits 0 and something breaks much later, with no obvious cause.


1. Make it yours

Two edits before anything else:

package.json — set the real package name. Scoped names are strongly preferred; an unscoped name can collide with anything on the public registry.

{
  "name": "@your-team/ui",
  "version": "0.1.0"
}

src/styles/tokens.css — this is where every colour, radius, space and font in the kit comes from. Change --ui-color-brand-500 and the whole library rebrands. No component hardcodes a value.

That is the entire rebranding surface. If you want to drive it from Figma instead of by hand, see Design tokens.


2. Architecture

src/
  index.ts                  root barrel
  styles/
    tokens.css              primitives  (--ui-*)          ← machine-owned
    theme.css               semantic roles + @theme inline ← hand-owned
    globals.css             entry: tailwind + tokens + base layer
    rc.css                  structural skin for rc-* widgets
  lib/                      cn(), shared variant fragments
  hooks/
  components/
    button/
      button.tsx
      button.stories.tsx
      button.test.tsx
      index.ts              ← this file is what creates the `/button` subpath

One folder per component. The folder boundary is not cosmetic: scripts/gen-exports.mjs scans src/components/*/index.ts and generates the exports map from it. That is what gives every component its own import path.

Three rules that keep the kit coherent:

  1. Components use only the semantic tokens (bg-background, text-primary-foreground, rounded-md). Never reference a --ui-* primitive directly — that is the indirection that makes a Figma re-sync restyle everything at once.
  2. Every component file that uses React state, effects or event handlers starts with 'use client', including its index.ts. Without it the kit breaks inside Next.js Server Components.
  3. rc-* packages are used for behaviour only. Never import their stylesheets — the skin belongs in src/styles/rc.css, written against the same tokens.

3. Design tokens

tokens.css holds raw values in oklch, grouped into ramps (neutral, brand, success, warning, danger), plus radius, spacing, type, elevation, motion and z-index scales.

theme.css maps those primitives onto roles (--background, --primary, --muted-foreground, …), defines the .dark overrides, and exposes everything to Tailwind via @theme inline.

The split exists so a design-tool sync can replace the primitives wholesale without touching the semantic mapping:

# with the Figma Dev Mode MCP server connected
/figma-sync <figma-file-url>

The figma-sync skill (in .claude/skills/) reads your Figma Variables, rewrites tokens.css, shows you the diff before writing, and updates the theme.css mapping only when a variable name has no counterpart. Without the MCP server it falls back to a Tokens Studio JSON export.


4. What ships

39 components, each with its own import path, story and — where there is behaviour to test — a test suite.

| Group | Components | | --- | --- | | Form | button input textarea label checkbox radio-group switch select combobox field form | | Layout & feedback | card badge avatar alert separator skeleton spinner progress | | Overlay | dialog sheet drawer dropdown-menu popover tooltip command | | Navigation | tabs accordion breadcrumb pagination toast | | Data & input-heavy (rc-*) | data-table virtual-list tree tree-select date-picker range-slider upload input-number |

The last row is where Radix stops. Each of those wraps an rc-* package for behaviour a primitive library does not cover — a table with pinned columns and expandable rows, a windowed list, a calendar that parses loose text input, a multi-thumb slider, an abortable upload. None of them imports its package's stylesheet; the skin is in src/styles/rc.css, written against your tokens like everything else.

Three notes on the edges of that list:

  • form is the react-hook-form binding. react-hook-form is an optional peer dependency, so form is reachable only at <pkg>/form — it is not in the root barrel, which would otherwise break consumers who do not use it. Everything else works without it. If you are not on react-hook-form, field gives you the same layout without the library.
  • toast wraps sonner and takes a theme prop (default 'system'); the kit intentionally does not depend on a theme library. Pass the value from your provider if your app lets users override the OS setting.
  • date-picker exports both DatePicker and DateRangePicker — they share one date-fns config, so they share one import path.

5. Adding a component

# Claude Code — scaffolds the folder, story, test, index.ts and regenerates exports
/ui-kit-component <name>

Or by hand:

npx shadcn@latest add dialog        # drops src/components/dialog.tsx
mkdir src/components/dialog && mv src/components/dialog.tsx src/components/dialog/
# add index.ts (with 'use client'), a .stories.tsx and a .test.tsx
pnpm exports:gen                    # regenerate package.json#exports

shadcn add writes a flat file; the kit uses folders. Moving the file and running exports:gen is the whole difference.


6. Build

pnpm build       # exports:gen → tsup → tailwind CLI
pnpm verify      # export map is current, every target exists, publint clean
pnpm test        # vitest, including the jest-axe smoke suite
pnpm typecheck
pnpm lint        # also fails on hardcoded colours and rc-* stylesheet imports
pnpm size        # per-subpath bundle budget

pnpm lint is where rules 1 and 3 above stop being conventions. In src/components/**, a --ui-color-* reference or a literal #hex/rgb()/oklch() is an error, and so is importing an rc-* stylesheet. The motion and z-index primitives (--ui-duration-*, --ui-ease-*, --ui-z-*) are allowed directly — nothing re-maps them per theme, so there is no semantic layer for them to skip.

pnpm test runs the per-component tests plus test/a11y.test.tsx, which puts jest-axe over every component in a realistic composition — a control with its label, a table with its header — since the violations that matter (missing accessible name, orphaned label) only appear once things are put together. Contrast is not checked there: jsdom has no layout engine, so that job belongs to @storybook/addon-a11y, which runs in a real browser and is configured to fail the story.

pnpm build produces:

| Output | What it is | | --- | --- | | dist/index.js / .cjs / .d.ts | root barrel, ESM + CJS + types | | dist/components/<name>/index.* | one entry per component | | dist/styles.css | compiled Tailwind — consumers need no Tailwind setup | | dist/tokens.css, dist/theme.css | raw token layers for apps that do run Tailwind |


7. Publishing

pnpm changeset          # describe the change, pick major/minor/patch
pnpm changeset version  # applies the bump + writes CHANGELOG.md
pnpm release            # guided publish

pnpm release checks the package name, the working tree, and whether that version already exists on the target registry; then builds, verifies, prints the tarball contents, and asks you to type the version number before it publishes. Pass --dry-run to run every check and stop short of publishing.

For a private registry, uncomment the matching block in .npmrc. Never hardcode a token there — use the ${NPM_TOKEN} form so the secret comes from the environment.


8. Consuming the published kit

No Tailwind in the consuming app — import the compiled stylesheet once:

// app entry
import '@your-team/ui/styles.css';

// anywhere
import { Button } from '@your-team/ui/button';

Tailwind v4 already in the app — take the tokens instead and let your own build generate the utilities:

@import 'tailwindcss';
@import '@your-team/ui/tokens.css';
@import '@your-team/ui/theme.css';
@source '../node_modules/@your-team/ui/dist';

Either way, importing from @your-team/ui/button pulls in Button and nothing else. The barrel (@your-team/ui) works too, but subpath imports keep cold builds and type-checking noticeably faster.

Dark mode is class-driven — put class="dark" on <html> (or let next-themes do it).