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

@hypersignals/design-system

v0.16.0

Published

Stackframe design system. Tokens and components shared by the myStacks wealth manager, client and admin dashboards.

Readme

@hypersignals/design-system

The Stackframe design system. Tokens and components shared by the myStacks wealth manager, client and admin dashboards.

| Consumer | Status | | --- | --- | | mystacks-fe-wm | consuming | | mystacks-fe-admin | not started | | mystacks-fe-client | not started |

Installing

bun add @hypersignals/design-system

No token, no .npmrc, nowhere. The package is public on npm, so it installs with no credential locally, in CI, or in a Vercel build. That is the whole reason it is published publicly rather than privately, and it is worth knowing what the alternatives cost: a private git dependency needs an SSH key or a PAT plus a URL rewrite in every CI and in Vercel, and GitHub Packages requires a token even for a public package, since its npm registry has no anonymous read.

Public does not mean freely reusable. The licence is UNLICENSED: the source is readable and installable, and no rights to use it are granted.

Peer dependencies you supply: react ^19, react-dom ^19, tailwindcss ^4, lucide-react >=1.

Wiring it up

Four steps. The third is the one people miss, because skipping it produces no error.

1. Transpile the package

// next.config.ts
transpilePackages: ["@hypersignals/design-system"],

Required. The package ships TypeScript source rather than a bundle, so Next has to compile it. That is deliberate: build tools routinely strip "use client" directives, which breaks React Server Component boundaries in a way that produces correct-looking markup that does nothing when clicked.

2. Import the tokens

/* src/app/globals.css */
@import "tailwindcss";
@import "@hypersignals/design-system/theme.css";

Every token, both themes, and the dark variant definition come from that one file. Do not redeclare a token in the app.

3. Provide the two font variables

theme.css reads --font-fraunces and --font-arimo:

--font-display: var(--font-fraunces, "Fraunces"), Georgia, serif;
--font-body: "Helvetica Neue", Helvetica, var(--font-arimo, "Arimo"), Arial, sans-serif;

Nothing here sets them, because loading a font is Next's job and this package has no next dependency. The consuming app loads both with next/font/google and puts the generated variables on <html>.

Both fallbacks resolve, which is why this is easy to get wrong. Miss this step and nothing errors: headings quietly render in Georgia and body text in Arial. Copy src/app/fonts.ts from an existing dashboard rather than writing it fresh, and read the comments there about why weight is omitted and why Arimo is present at all.

4. Let Tailwind scan the package

Usually nothing to do. theme.css carries @source "../", which points Tailwind at this package's own files.

That directive is load-bearing now in a way it was not before. While the design system was a workspace folder inside the WM app, packages/ was an ordinary tracked directory that Tailwind's automatic source detection already covered, so @source was belt and braces. Installed from npm the package lives in node_modules, which that detection deliberately skips, so @source is the only reason its classes reach the build at all. Verified working across that boundary: all 33 package-exclusive classes reach the WM dashboard's stylesheet.

The failure mode is missing styles, not an error, so do not trust a green build. Each dashboard runs bun run check:tailwind after its build, which asserts that package-exclusive classes reached the emitted stylesheet.

Importing components

Granular subpaths, one per component:

import { Button } from "@hypersignals/design-system/button";
import { buttonVariants } from "@hypersignals/design-system/button.variants";
import { cn } from "@hypersignals/design-system/cn";

There is no index barrel, on purpose: with one, a server component importing Button would pull every client component in the package into its module graph.

Never import @hypersignals/design-system/src/.... Use a declared subpath. Anything not in the exports map is internal and may move without a major version.

Adding a component

  1. Start from shadcn if there is one, using components.json. That file came across with the extraction and has not been exercised since, so on first use check where the CLI actually put the files.
  2. Restyle it to Stackframe. A component pulled in from shadcn becomes ours; nothing is exempt from the rules below.
  3. One component per file. A variant definition goes in <name>.variants.ts and a helper in its own module, so each can be unit tested alone.
  4. Add a test next to it.
  5. Add the subpath to exports in package.json.
  6. bun run check.

Step 5 is enforced. check-package-exports.ts fails on an entry pointing at a missing file and on a component that exists but is not exported, because the first breaks a consumer's build and the second reads as a missing feature rather than a missing line of config. It was written after a ./logo entry pointed at a file nobody ever wrote.

Rules

  • No hardcoded values. Every size, space, radius, colour, shadow, duration and easing comes from a token or a Tailwind scale step. A bracket is for what the scale cannot say: a ratio, a percentage, a CSS function, a character measure, a variant selector. Never a measurement. When the design's value is not on the scale, force the nearest step; 13px is py-3.25 and 52px is h-13. check:scale enforces this.
  • No environment. This package cannot read process.env. A component coupled to one app's configuration cannot be shared. eslint enforces it.
  • No imports from a consuming app, and none by its own package name. Relative paths within the package. eslint enforces both.
  • Filenames are kebab-case; the exported component stays PascalCase.
  • No em dashes or en dashes anywhere. check:em-dash enforces it.
  • Comments explain why, never what.

Developing

bun install
bun run check        # typecheck, biome, eslint, em dash, hardcoded values, exports map
bun run test         # vitest with the coverage gate
bun run test:watch   # iteration without the gate
bun run verify       # everything CI runs

Two TypeScript programs, and the split matters. tsconfig.json checks shipped code the way a consumer compiles it: React types only, no Node, no test globals, tests excluded. tsconfig.tools.json checks everything that never ships, which is where @types/node and the jest-dom matcher augmentation live. Both run under bun run typecheck.

While building a dashboard against it

Do not develop a new dashboard against pinned tags. Every screen will want a component that does not exist yet, and bumping a tag per component across repos is miserable. Point the consumer at a local checkout instead:

cd mystacks-design-system && bun link
cd ../mystacks-fe-admin && bun link @hypersignals/design-system

Pin a real tag once the component set settles.

Releasing

Bump the version, tag it, push the tag. .github/workflows/publish.yml does the rest.

npm version minor          # or patch / major, which writes package.json and commits
git push origin main --follow-tags

The workflow runs the full verify before publishing, refuses if the tag and package.json version disagree, prints the tarball contents to the log, and publishes with --provenance so the package is linked to the workflow run that built it. It needs one secret, NPM_TOKEN, an npm automation token; that is the only credential in this whole setup, and it is needed only to publish, never to install.

A publish is close to irreversible, which is why verify runs first rather than after. npm allows unpublishing only within 72 hours, and refuses entirely once something depends on the version. Prefer releasing a new patch over trying to withdraw one.

Then bump the range in each consuming dashboard. Renovate handles this as an ordinary npm dependency now, so those pull requests arrive on their own.

Coordinated releases are not needed for this package: a dashboard sitting two versions behind on a Button has a cosmetic lag and nothing worse. That will not be true of the platform kernel, the Privy and API code that is the next candidate for extraction. The aud pin in its token verification is the only thing keeping one dashboard's tokens from authenticating against another's surface, so a stale copy there is a tenant isolation failure rather than a visual bug. When that package exists, pin it exactly, never automerge it, and treat a release as unfinished until all three dashboards are bumped. It should almost certainly be private, for the same reason: an auth kernel is not a palette.

Why public npm

This was consumed as a private git dependency first, under the @mystacks scope. Recording why it changed, because the reasons are not obvious and the alternatives look cheaper than they are.

Every private option needs a credential in three places. Not just CI: a Vercel build runs bun install too, and so does each developer's laptop. A private git dependency needs an SSH key or a PAT with a URL rewrite. GitHub Packages needs a token even for a public package, because its npm registry has no anonymous read, unlike ghcr.io for containers. Public npm needs none, anywhere. With three dashboards, that is nine places not to configure and nine ways for a build to fail for a reason that has nothing to do with the code.

Git dependencies have sharp edges beyond the token. The github:owner/repo shorthand and git+https both resolve through the GitHub tarball API, which 404s on a private repo and honors no token environment variable, so only git+ssh:// works at all. There are no semver ranges, so every bump is a hand-edited pin in each consumer. And moving a tag does not invalidate bun's cache: a consumer silently keeps installing the old commit until someone runs bun pm cache rm, which is a genuinely nasty way to lose an hour.

The cost was the scope rename, @mystacks to @hypersignals, the npm organisation. That came to 100 import lines across 44 files in the WM dashboard, done with one sed and verified by typecheck. Cheap, and it only happens once.

Note the npm scope is @hypersignals while the GitHub organisation is hsignals. Nothing requires those to agree. Only GitHub Packages ties a scope to the repository owner, and that registry is not used here for exactly the reason above.

The remaining cost is the real one: the source is world readable. The package deliberately carries no brand assets, no business logic, no endpoints and no secrets, so what is exposed is the palette, the two typefaces, and a set of radix wrappers.

Why the package is the repo

Originally forced. A git dependency installs a whole repo, and neither bun nor npm can point at a subdirectory, so a consumer aimed at a monorepo installed the workspace root, whose package.json has no exports map, and every subpath import failed to resolve.

Publishing to a registry removes that constraint, because a tarball is built from whatever directory you publish from. So a monorepo holding this, the platform kernel and the test kit is possible again. Left as one repo per package on purpose: this works, and the shape is worth revisiting when there is an actual second shared package rather than in anticipation of one.

Known defect: light theme contrast

White on cyan, the label of every primary button, measures 2.67:1 and fails every WCAG threshold including the 3:1 non-text floor. The dark theme already solves it with near-black on cyan at 6.99:1.

src/styles/contrast.test.ts records the full measured set and is a record as much as a test: if a pair changes category, the test fails and someone has to update the expectation deliberately. There is no axe exception, so this will fail a dashboard's e2e accessibility test as soon as a primary button renders on a tested page. That is intentional, and it is a recorded decision rather than an open question.

Decided 14 August 2026: it stays. That closes the ambiguity but not the consequence, and the two are worth keeping apart. The pair still measures 2.67:1, a consuming dashboard's axe run will still fail the first time a primary button renders on a tested page, and the fix is still the one dark theme already uses: near-black on cyan, which lands near 5.6:1 in light while keeping both the palette and "cyan is the only action colour" intact.

So a consumer that hits this needs a scoped axe exception citing the decision, in the same shape as the ones for muted-foreground and the link colour. What it must not do is disable the contrast rule, which would take every other pair with it.

Test coverage

28 files, 194 tests. The coverage gate sits just under what the suite achieves: 82 statements, 76 branches, 81 functions, 82 lines.

Those numbers are measured here and are deliberately not WM's, which are roughly twelve points higher because they were computed over the app's services and lib alongside these components.

What is still uncovered is the same category it always was, thin radix wrappers where the behaviour worth testing is focus or portalling and belongs in a browser rather than in happy-dom: select, tooltip, dropdown-menu, textarea, skeleton, progress-bar, checkbox, character-count, google-mark, and now dialog.

Thresholds are a ratchet. Raise them; never lower one to make a change pass. They have moved twice on the dashboard component set, from 78/70/75/78 to 82/76/81/82.