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

@verndale/ui-design-library

v4.5.1

Published

Public React component library keyed by ui-design-brain canonical slugs.

Readme

ui-design-library

Public npm component library for Verndale's frontend platform. Components are keyed by ui-design-brain canonical slugs, so a resolved design label maps straight to an implementation.

The catalog says what a Modal is. This repo is the Modal.


Why it exists

Every project rebuilds the same dialog. Not because anyone wants to, but because the previous project's version is buried in a client repo, wired to that client's CMS and tokens, and nobody can tell from outside whether it was any good.

This library is where a component goes once it has earned reuse. Its contract is the catalog's vocabulary:

ui-design-brain     resolves "dialog" → canonical Modal (slug: modal)
ui-design-library   components/modal/ → the implementation

That is the whole mechanism. Because both sides key on the same slug, a build pipeline that has resolved a label can look up an implementation deterministically instead of generating one from scratch.


Layout

components/<slug>/                    a canonical's default implementation
├── index.ts                          stable public facade for the package subpath
├── <Component>.types.ts              public prop and supporting types
├── <Component>.tsx |                 server-compatible tree, or
│   <Component>.client.tsx            client-only tree when wholly interactive
├── parts/                             private branches, leaves, and narrow client islands
├── <Component>.stories.tsx           the API contract — see below
└── component.json                    canonical, API slots, reuse fingerprint, variants,
                                      tokens, provenance

components/<slug>--<variant>/         a structurally-distinct implementation of the same
                                      canonical (optional) — the same internal contract

src/tokens/                           the semantic token layer (the styling contract)
src/lib/                              shared, dependency-free primitives (focus, scroll)

A canonical resolves to a directory. In the common case that is one implementation — components/<slug>/, <slug> == kebab(canonical), matching the catalog. When a canonical needs more than one structurally distinct implementation (a plain nav bar and a mega menu, both Navigation), the default stays the bare components/<slug>/ and each alternate is a sibling components/<slug>--<variant>/. Both carry slug == kebab(canonical); variant (singular — the structural axis, not the prop-value variants array) distinguishes them, and one is the default. The key is (canonical, variant). Single-variant components need neither field. pnpm contracts enforces all of it.

Server-first component boundaries

Every public subpath resolves through components/<slug>/index.ts. That facade is stable; the tree/branch/leaf files behind it can change without changing the consumer import. Stories also import only ./index, so Storybook exercises the same API consumers receive.

Components are server-compatible by default. Browser state, effects, portals, focus management, and DOM observers belong in files named *.client.tsx or *.client.ts, with 'use client' at the narrowest useful boundary. Every client module is limited to 120 physical lines. A hybrid component such as Accordion keeps its outer tree server-compatible and hydrates its disclosure leaves; a wholly interactive component such as Modal exposes a client facade.

The architecture gate also requires a separate types module and at least two meaningful non-story TSX modules per component. It rejects client hooks or browser globals in neutral modules, render-time browser access, misplaced client directives, and story imports that bypass the facade. pnpm test:ssr renders public components without browser globals.

The core stays React- and Tailwind-based rather than Next-specific. No component may import next/*. Next is a development-only consumer fixture exercised by pnpm test:next, not a runtime or peer dependency.


The story file is the API contract

Not documentation — the contract. argTypes declares every prop with its control and description, so both a developer browsing Storybook and an agent reading the file get the same surface. A component without stories fails pnpm contracts, because an implementation nobody can inspect is not reusable.

pnpm storybook          # browse at http://localhost:6006
pnpm build-storybook    # static build

The addons that carry real weight:

  • @storybook/addon-docstags: ['autodocs'] in .storybook/preview.ts generates a Docs page per component: the description, a live preview, and a prop table built from the TypeScript types and argTypes. Without that tag the addon is installed and produces nothing, so leave it on.
  • @storybook/addon-a11y — runs axe against every rendered story and reports violations, passes, and inconclusive results in the Accessibility panel. Accessibility is most of why a captured component is worth keeping.
  • @storybook/addon-vitest — runs every story as a test in a real Chromium and reports in the sidebar Testing widget. It is also what makes the a11y check a gate rather than a suggestion.
  • storybook-addon-pseudo-states — forces :hover, :focus-visible, :active and friends as static states from the toolbar. Most of this library's behaviour lives in those states, and :focus-visible cannot be inspected by hand at all: it deliberately does not match a click. It also makes the a11y panel usable on focus states, which is where focus-ring contrast problems actually are.
  • storybook-addon-tag-badges — renders each component's maturity in the sidebar. Configured in .storybook/manager.ts, not preview.ts — the addon reads addons.getConfig(), so a preview parameter is silently ignored.

Two toolbar controls are configured rather than installed, since Storybook 10 ships both in core:

  • Viewport — includes the two library breakpoints (lg, xl) alongside the device presets. Breadcrumbs collapses below xl and Modal goes full-screen below lg; both are otherwise only checkable by dragging the window.
  • Backgrounds — named for the semantic tokens (surface-base, surface-inverse, …) rather than colours, so a project overriding a token sees the override here too.

The direction toggle

Every component uses logical properties (ps/pe, ms/me, border-s) so it works in both writing directions. That only holds if somebody can see the other direction — an incomplete conversion reads as perfectly correct in LTR. The Direction toolbar control flips the preview.

It is a local decorator (.storybook/withDirection.tsx), not a dependency, and it sets dir on documentElement rather than on a wrapper: Modal renders through a portal into document.body, so a wrapper would leave the component with the most to get wrong in RTL still rendering LTR.

This is not theoretical. Quote shipped with logical padding and a physical border-l, so in RTL the accent rule and the text sat on opposite sides.

Accessibility is enforced

preview.ts sets a11y: { test: 'error' }, so an axe violation fails the story test. That is backed by a real runner: pnpm test:stories renders every story in Chromium, runs its play function, and runs axe over the result.

Not every axe finding is a real defect. Where a rule is wrong for a specific story — a disabled control tripping color-contrast, which WCAG 1.4.3 explicitly exempts — scope the rule off on that story with a comment saying why, rather than loosening the global setting:

parameters: { a11y: { config: { rules: [{ id: 'color-contrast', enabled: false }] } } }

The pnpm/aria-query workaround

Wiring this up was previously abandoned as blocked: Vitest browser mode served the CJS aria-query raw and every story failed with does not provide an export named 'elementRoles'. The cause is pnpm's isolated node_modules — neither aria-query nor @testing-library/dom resolves from the project root, so naming either in optimizeDeps.include silently does nothing. Pre-bundling the resolvable ancestor works:

optimizeDeps: { include: ['storybook/test'] }

vitest.config.ts also re-declares the Tailwind plugin. It replaces vite.config.ts for the test run rather than extending it, and without Tailwind every story renders unstyled — which quietly invalidates any computed-style or contrast assertion while still reporting green.


Tokens are the portability contract

Components reference only semantic tokens — bg-surface-raised, text-text-primary, px-page-margin. Never a hex value, never a client's brand name. pnpm contracts fails on a raw colour in a component.

A consuming project re-themes the library by overriding the custom properties in src/tokens/semantic.css with its own values. It never edits a component. That is what makes the same Modal render in one client's palette and another's without a line changing.

The defaults here are deliberately unbranded. A component shipping a client's red is that client's component, not a library one.


Consuming it

Install one exact version. The exact dependency is the orchestration pipeline's package-reuse opt-in; ranges, tags, aliases, workspace links, and file: dependencies are deliberately not accepted.

pnpm add --save-exact @verndale/ui-design-library@<exact-version>

Import Tailwind, the library's semantic layer, and one explicit source path in the application's global stylesheet. @source paths are relative to the stylesheet containing the directive, so adjust the example's ../ segments for the consuming project.

@import "tailwindcss";
@import "@verndale/ui-design-library/styles.css";
@source "../../../node_modules/@verndale/ui-design-library/dist";
import { Modal } from '@verndale/ui-design-library/components/modal';

The import path is the same for a server-compatible component:

import { Alert } from '@verndale/ui-design-library/components/alert';
import { Button } from '@verndale/ui-design-library/components/button';

export function SaveForm() {
  return (
    <form action="/save">
      <Alert>Ready to save.</Alert>
      <Button type="submit">Save</Button>
    </form>
  );
}

Interactive callback APIs are used from a Client Component:

'use client';

import { DismissibleAlert } from '@verndale/ui-design-library/components/alert';

export function SavedNotice({ onClose }: { onClose: () => void }) {
  return <DismissibleAlert onDismiss={onClose}>Saved.</DismissibleAlert>;
}

A structural variant lives in its own directory; both satisfy the same catalog canonical — import the structure the design resolved to:

import { Navigation } from '@verndale/ui-design-library/components/navigation';
import { MegaMenu } from '@verndale/ui-design-library/components/navigation--mega-menu';

There is no root barrel and no short alias such as @verndale/ui-design-library/modal. The directory-shaped subpath is the public identity. Override semantic tokens in the consuming project's own layer; never edit the installed package.

When upgrading across the server-first architecture release, see MIGRATION.md for the Button, Alert, Badge, and Carousel API changes.

The package includes each implementation's source, story, and component.json for deterministic orchestration inspection. Package-level uiDesignLibrary.reuseContractVersion identifies the metadata contract. Each manifest names one primary AI candidate with exportName and its derived rendering boundary (server, hybrid, or client). Secondary developer exports remain public but are not separate candidates.

reuseFingerprint is separate from API-level slots: it describes only the primary export and uses the pipeline's governed structural slots + affordance + role triad. Governed other values are valid metadata but intentionally never auto-match. variant remains the singular structural implementation axis, while variants remains the primary export's list of prop/style values. Entries in variants are unique non-empty strings; an empty array means the primary export has no governed style values.

Releases

Every merge to main runs the full test/build/pack gate and semantic-release. A breaking change publishes a major, feat publishes a minor, and every other permitted conventional-commit type publishes a patch. Tags and GitHub releases use v<version>; the source package.json stays 0.0.0-development.

Publishing uses npm trusted publishing for the verndale/ui-design-library repository and .github/workflows/release.yml. The workflow requests id-token: write and carries no long-lived npm token. Configure that trusted publisher before merging a release-producing PR, then remove any obsolete NPM_TOKEN repository secret.

GitHub squash merges must use the PR title as the commit subject and a blank commit description. Intentional breaking releases put ! in that title (feat(package)!: ...). The release preflight checks every commit after the latest reachable v<semver> tag and rejects BREAKING CHANGE text in any body, so a failed release followed by a clean merge cannot hide stale text that semantic-release would still analyze.


How a component gets here

  1. A project-retrospective run produces a capture — a mature implementation, its evidence, and an exhaustive list of the client coupling that has to come out.
  2. A human executes the capture: rewrites the component against the library's tokens and primitives, writes the stories, fills in component.json.
  3. It lands as maturity: "candidate". Promotion to supported is a separate, deliberate decision.

Captures usually come from labels that already resolve — a mature Card or Modal — not novel ones. Novel labels are typically the least settled code in a project.

Step 2 is a rewrite, not a copy. component.json's declienting array records exactly what was stripped, so the cost is visible rather than folklore.

A capture that is a structurally distinct take on a canonical the library already ships — a mega menu where Navigation ships a plain bar — lands as a new components/<slug>--<variant>/ directory rather than overwriting the incumbent. Its component.json shares the canonical and slug and sets variant; the incumbent gains variant and default: true. This is the one case where a second capture of the same canonical does not replace the first.


Quality gates

pnpm test              # typecheck + lint + contracts + SSR + Chromium/WebKit/mode/motion tests
pnpm typecheck         # tsc --noEmit
pnpm lint              # hooks, boundaries, and source linting
pnpm architecture      # index/types/parts shape, client naming + 120-line ceiling
pnpm test:ssr          # public components render with no browser globals
pnpm build             # native Node ESM + declaration build, then export/dist parity
pnpm exports:check     # component directories and committed package exports agree
pnpm exports:sync      # deliberately update the committed map after adding/removing a component
pnpm contracts         # slug/canonical agreement, the variant axis, declared tokens exist,
                       # no raw colours, provenance/stories/maturity, reuse fingerprint
pnpm contracts:selftest # exercises the contract checker itself against fixtures
pnpm accessibility    # every story rendered in Chromium: play functions + WCAG 2.2 A/AA axe
pnpm test:a11y:webkit # the same stories in WebKit (a Safari-engine proxy)
pnpm test:a11y:modes  # 320px, 200% text, WCAG text spacing, and forced colors
pnpm test:stories      # compatibility alias for the Chromium accessibility suite
pnpm test:stories:watch # the same, in watch mode
pnpm test:motion       # `motion`-tagged stories re-run under prefers-reduced-motion
pnpm verify            # test/build + packed native-ESM imports + Next/Tailwind consumer fixture

pnpm test:stories needs a browser binary. Once per machine:

pnpm exec playwright install chromium webkit

Environment

  • Node 24+, pnpm 10+ via Corepack. pnpm install.
  • React 19, Tailwind v4, Storybook 10, Vite 7.

Context wiki

wiki/ records why the repo is the way it is: executed plans, the decisions behind them, and what was ruled out. Start at wiki/INDEX.md and open only the page it routes to.

wiki/
├── INDEX.md          # read this first — routes to everything else
├── MECHANICS.md      # the capture protocol
├── topics/           # per-subsystem design history
├── journal/          # one entry per substantive change
└── plans/            # executed plans + an audit table

Modelled on ui-design-brain's wiki, including its knowledge graph (pnpm graph:build / pnpm graph:view, scripts/graph/README.md) and wiki-sync bot automation, adapted to this repo's shape. What's still different is listed at the bottom of INDEX.md.

Related

  • ui-design-brain — canonical vocabulary; this library implements it
  • project-retrospective — produces the captures
  • ui-design-evidence — retrospective runs and the cross-project graph