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

@5voltfx/design-system

v0.6.1

Published

A shared React component library and token system for the three 5VoltFX/Matt Welker sites — `song-chart`, `mattwelker.com`, and `5voltfx.com`. Plain React 19 and plain CSS custom properties, no UI framework.

Readme

@5voltfx/design-system

A shared React component library and token system for the three 5VoltFX/Matt Welker sites — song-chart, mattwelker.com, and 5voltfx.com. Plain React 19 and plain CSS custom properties, no UI framework.

The system has a point of view: it is drawn as a drafting document. Components are specimens on a ruled ground, named and specified. Two of the eight themes (instrument, instrument-day) lean all the way into that; the other six are painted themes that inherit the same structure with a softer finish.


Seeing it

The playground/ app is the live style guide. It consumes the package exactly the way the three sites do, so what you see there is what you get.

cd playground
npm install        # first time only
npm run dev        # http://localhost:5173/

Theme switcher is in the footer — all eight themes, live.

| Route | What it shows | |---|---| | / | Tokens — color families, type scale, spacing, elevation, radius, motion | | /primitives | Button, Card, Badge, Heading, Text, Container/Stack/Grid | | /plates | Plate anatomy — Plate, Specs, Caption, Readout, Chapter | | /forms | FormField, Label, Input, Textarea, Select, Checkbox, Radio, Switch | | /overlays | Modal, Menu, Tabs, Accordion | | /navigation | SlideRuleNav and DialNav, all six variants |

Working on the library and the playground at once

The playground depends on the package via file:.. and resolves to dist/, not src/. So run the library's watch build in a second terminal:

npm run dev        # in the repo root — tsup --watch, rebuilds dist/

The playground's Vite config uses the watchDesignSystem() plugin (exported from @5voltfx/design-system/vite-plugin) to watch dist/ and force a full reload. Without both halves running, source edits will not appear.

Other scripts:

npm run build      # tsup + tsc — bundles dist/ and emits the .d.ts files
npm run typecheck  # tsc, no emit
npm test           # vitest

npm run dev does not regenerate type declarations. It runs tsup only; the .d.ts files come from the tsc half of npm run build. Watch mode leaves the existing declarations in place rather than deleting them, so editor autocomplete keeps working — but it goes stale the moment you change a component's props. Run npm run build before publishing, or whenever you want types that match the source.

Blank page? Clear the Vite cache

rm -rf playground/node_modules/.vite && cd playground && npm run dev

Vite pre-bundles dependencies into node_modules/.vite/deps and stamps every dep URL with a browserHash recorded in deps/_metadata.json. That cache is keyed off the lockfile and config — not off the contents of a file:-linked package. So when dist/ gains a new export, the cache doesn't notice, the browser links against the old module, and you get a blank page with one console error naming the missing export:

SyntaxError: The requested module '/node_modules/@5voltfx/design-system/dist/index.js?v=<hash>'
does not provide an export named 'Badge'

Misleading, because the export is there — grep 'Badge' dist/index.js finds it, and so does curling the URL. Only the browser's module graph is behind. A hard reload does not clear it; deleting .vite does.

Expect this in the consuming sites too, for the same reason. Suspect it whenever a component that exists everywhere on disk is "not exported."


Installing in a site

npm install @5voltfx/design-system
import "@5voltfx/design-system/styles.css";   // tokens + all component CSS
import { ThemeProvider, Header, Button } from "@5voltfx/design-system";

ThemeProvider must wrap the app — it sets data-theme on <html>, which is what every token resolves against. Components that link (Header, Nav, SlideRuleNav, DialNav with to) need a react-router-dom Router above them.

Fonts are not injected. The package deliberately ships no font-loading side effects. Add the <link> yourself:

<!-- Space Grotesk — the body face for the six painted themes -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">

<!-- Only if you use the instrument themes -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo+Narrow:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Source+Serif+4:ital,wght@0,400;0,600;1,400;1,600&display=swap">

Peer dependencies: react ^19, react-dom ^19, react-router-dom ^7.


Vocabulary

Words this system uses in a specific way. Using them precisely is the fastest way to be understood — by a person or by Claude.

Plate — a documented specimen. A Card with a head (number, name, kind), a stage (the thing itself), and a foot (specs, caption). Rendered as a <figure> so the specification is bound to the specimen, not floating after it.

Ground — the ruled desk a plate sits on. Painted from --ds-color-ground-grid, which is zero-alpha in every theme that doesn't want one. Lands on html/body automatically, or on any .ds-ground container.

Finish — the shape and shadow a theme is machined to: corner radius, shadow, registration marks. The instrument themes override it to hard 3px corners and a hard offset shadow. Palette alone can't get there — a 16px-rounded card under a blurred shadow reads as soft whatever colors are in it.

Rule — the component tokens for the two slide-rule navigations (--ds-rule-*).

The three voices — a drafting document doesn't set everything in one face:

| Token | Role | |---|---| | --ds-font-display | names things — headings, control labels | | --ds-font-mono | reads values — specs, readouts, numerals | | --ds-font-note | explains — body prose, captions |

All three default to --ds-font-body, so the six painted themes pay nothing for them. The instrument themes opt in: Archivo Narrow / JetBrains Mono / Source Serif 4.

The accent's two jobs — --ds-color-bg-2 is the accent as a fill (something must be readable on it, via --ds-color-on-accent); --ds-color-font-2 is the accent as text (it must be readable on the surface behind it). These pull in opposite directions. Never substitute one for the other — say which you mean.

Station — one selectable position on SlideRuleNav or DialNav. Between 2 and 8; past 8 the extras aren't rendered and the nav warns in dev.

Detent — the notched travel between stations, at quarter-tick intervals.


Tokens

All tokens are CSS custom properties prefixed --ds-, defined in src/tokens/ and imported in that order by src/tokens/index.css.

Color

Every theme overrides the full set inside its own [data-theme="…"] block. The :root values are the default (coral) theme, so components render sensibly before ThemeProvider runs.

| Family | Meaning | |---|---| | --ds-color-bg-0 … bg-4 | Surfaces. bg-0 is the page ground, bg-2 is the accent as fill | | --ds-color-font-0 … font-4 | Text. font-0 is primary copy, font-1 muted, font-2 the accent as text | | --ds-color-bw-0 … bw-4 | Neutral ramp — borders, rules, dividers | | --ds-color-on-accent | Text that sits on bg-2. Chosen per theme, not pinned to font-0 | | --ds-color-url, --ds-color-visited | Link states | | --ds-color-highlight | Selection/emphasis | | --ds-color-ground-grid | The ruled grid. Zero-alpha unless a theme opts in |

Status colors (semantic.css) are theme-agnostic: --ds-color-danger, -success, -warning, -info, each with a matching --ds-color-on-*. All four fills carry white. instrument-day darkens all four, because it's the one genuinely light surface and they landed between 2.0:1 and 3.3:1 on it.

Form chrome is single-sourced so every control matches: --ds-color-field-bg, --ds-color-field-border, --ds-color-focus-ring.

Typography

Modular scale, ~1.25 ratio, base 1rem: --ds-font-size-100 (0.8rem) → --ds-font-size-700 (3.052rem).

Weights --ds-font-weight-regular|medium|semibold|bold. Line heights --ds-line-height-tight|normal|relaxed. Display tracking via --ds-tracking-display (normal by default; 0.06em on the instrument themes, because condensed faces want air).

Spacing

--ds-space-1 (0.25rem) through --ds-space-8 (4rem): 0.25 / 0.5 / 0.75 / 1 / 1.5 / 2 / 3 / 4 rem.

Stack and Grid take the bare number as their gap prop — gap="5" resolves to var(--ds-space-5).

Radius, elevation, motion, z-index

| Group | Tokens | |---|---| | Radius | --ds-radius-sm 6px, -md 10px, -lg 16px, -pill 999px | | Elevation | --ds-shadow-sm|md|lg — tinted with the theme accent at low opacity, not flat black | | Motion | --ds-motion-duration-fast 120ms, -base 200ms, -slow 320ms; --ds-motion-ease-standard | | Z-index | --ds-z-base 0, -dropdown 1000, -sticky 1100, -overlay 1200, -modal 1300, -popover 1400, -toast 1500 |

The instrument themes reduce radius to 2–3px and swap the diffuse shadow for a hard offset one. --ds-radius-pill is deliberately left alone — a caller asking for a pill is asking for a specific geometry, not the theme's default corner.


Themes

Eight, applied by ThemeProvider setting data-theme on <html>.

| id | Character | |---|---| | coral | Creative warm — the default | | ink | Dark indigo | | sage | Muted olive | | midnight | High contrast | | amber | Golden | | harvest | Earthy warm | | instrument | Drafting, dark stock | | instrument-day | Drafting, celluloid — the one genuinely light theme |

The first six are painted themes: palette only, soft finish. The two instrument themes additionally take the three type voices, hard corners, an offset shadow, corner registration marks, and the ruled ground.

All eight are measured at zero WCAG AA failures across tokens, primitives, plates, forms, overlays, and navigation. src/tokens/themes.test.js asserts this — if you add or move a color, that suite is the gate.

<ThemeProvider storageKey="myapp.theme" defaultTheme="instrument">

| Prop | Default | Notes | |---|---|---| | storageKey | "ds.theme" | localStorage key; syncs across tabs | | defaultTheme | "coral" | | | onThemeChange | — | Called with the new theme id |

useTheme() returns { theme, setTheme, themes } and throws outside a provider. THEMES, THEME_IDS, and DEFAULT_THEME are exported for building your own picker.


Components

Everything imports from the package root. All components take className and forward unknown props to their root element. Many take as to change the tag.

Primitives

| Component | Key props | |---|---| | Button | variant primary|secondary|ghost · size sm|md|lg · shape sharp|soft|rounded|pill · icon · as | | Card | as | | Badge | variant solid|outline|accent|brass|good|warn|quiet · as | | Heading | level 1–6 · size 200–700 (defaults from level) · decorative | | Text | as · size 100|200|300 (default 200) · tone default|muted |

Heading is set in the display voice and Text in the note voice, so the instrument themes get the type split for free without any component change.

Button is flat and 2D on purpose — no elevation, no hover lift. Hover and press are communicated with color only. Its icon wrapper is aria-hidden, so the label text must carry the meaning.

Layout

| Component | Key props | |---|---| | Container | as — centered 1200px max-width, responsive gutters | | Stack | gap (space step, default "4") · direction (default column) · align · as | | Grid | columns (default 2) · gap (default "4") · as |

Plate anatomy

The system's distinctive layer — a documented specimen and the parts that describe it.

<Plate
  number="L-I"
  name="Cursor rule"
  kind="Top nav"
  specs={[["Stations", "5"], ["Travel", "Detented"]]}
  caption="A fixed scale with a cursor that travels over it."
>
  <SlideRuleNav variant="cursor" items={items} />
</Plate>

| Component | Key props | |---|---| | Plate | number · name · kind · specs (array of [key, value]) · caption · as (default figure) | | Specs | items — array of [key, value]; renders a <dl> | | Caption | as (default p) — the explaining voice, in prose | | Readout | fields [{label, value, kind}] · status {text, tone: "lock"\|"drift"} | | Chapter | as (default h2) — a section rule with a label set into it |

Every part of Plate is optional. With none of them it is a Card, which is what it's built on — so it inherits the theme's finish, registration marks included.

Readout is presentational and announces nothing. A component whose value changes during a drag owns its own live region, so only committed changes are announced.

Chrome

| Component | Key props | |---|---| | Header | brand · brandHref (default /) · links · actions | | Nav | links — [{ to, label, end }], active state via NavLink | | Footer | brand · showThemeSwitcher (default true) · left · right | | ThemeSwitcher | — swatch row, one per theme |

Header and Footer are shells with slots. Each site's bespoke chrome — song-chart's auth-aware menu, mattwelker.com's audio toggle and Venmo link, 5voltfx.com's logo fallback — gets passed in rather than encoded here.

Forms

| Component | Key props | |---|---| | FormField | label · help · error · required · htmlFor · single control as child | | Label | required | | Input | size sm|md|lg · invalid · all native <input> props | | Textarea | rows (default 4) · invalid | | Select | size · invalid · <option> children | | Checkbox | label | | Radio | label · value | | RadioGroup | name · value · onChange · options [{value, label}] or Radio children | | Switch | label |

FormField does the accessibility wiring: generates an id, links the label via htmlFor, connects help/error through aria-describedby, and sets aria-invalid plus the control's invalid styling when error is present. Prefer it over hand-wiring.

<FormField label="Email" help="We never share it." error={err} required>
  <Input type="email" value={v} onChange={onChange} />
</FormField>

Every control is a styled native element — Checkbox, Radio, and Switch put a styled indicator over a real <input>, and Select stays a real <select> — so keyboard, focus, and toggle behavior are native and free.

Overlays

| Component | Key props | |---|---| | Modal | open · onClose · title · size sm|md|lg | | Menu | trigger · items [{label, onSelect, disabled}] · align start|end | | Tabs | items [{id, label, content, disabled}] · value/defaultValue · onChange | | Accordion | items [{id, title, content}] · multiple · defaultOpen |

Modal portals to document.body and handles focus trap, Escape, backdrop click, scroll lock, and focus restore. Menu uses roving focus with arrow keys and closes on outside click or Escape. Tabs is uncontrolled unless you pass value; arrows move selection, Home/End jump to the ends.

Slide-rule navigation

The signature pieces — navigation drawn as instrument mechanisms, with detented travel between stations.

<SlideRuleNav variant="vernier" items={items} readout label="Main" />
<DialNav variant="volvelle" items={items} label="Sections" />

| Component | Variants | Key props | |---|---|---| | SlideRuleNav | cursor · slide · vernier (default) | items · value/defaultValue · onChange · readout · label | | DialNav | volvelle (default) · concentric · fan | items · value/defaultValue · onChange · expanded/defaultExpanded · onExpandedChange · label |

Items are { id, label, to?, end?, onSelect?, value? }. With to they render NavLinks and need a Router; without, they call onSelect.

SlideRuleNav is the linear form, meant for a top nav. cursor and vernier hold a fixed scale and travel a cursor over it; slide inverts that — the index is fixed and the slide itself travels, carrying its stations with it.

DialNav is the circular form, meant for a sidebar that extends on hover. volvelle spins its rail under a fixed index; concentric is two-level; fan holds its spokes still and sweeps a pawl, righting only the indexed label.

Both clamp to 2–8 stations and respect prefers-reduced-motion. An unknown variant falls back to the default and warns in dev rather than throwing.


Conventions

Class names are BEM-ish and ds--prefixed: ds-button, ds-button--primary, ds-button__icon. Block, --modifier, __element.

Every component takes className and merges it after its own classes, so a consuming site can always override.

Polymorphism via as where the tag is a legitimate choice (Button as="a", Chapter as="div"). Not everywhere — Plate's foot switches between figcaption and div based on as, because figcaption is only valid inside a figure.

Structure over decoration. Specs is a <dl> because each key names a property and each value gives its measurement. Plate is a <figure>. Chapter is a heading by default, because a document divider that isn't in the outline is a decoration.

No side effects beyond CSS. No font loading, no global listeners at import time. sideEffects is declared as **/*.css only, so tree-shaking works.

Contrast is a gate, not a preference. All eight themes are at zero AA failures and themes.test.js enforces it.


Layout of the repo

src/
  index.js              # the public surface — every export, grouped
  tokens/               # CSS custom properties, imported in order by index.css
    colors themes base ground typography spacing radius
    elevation motion zindex semantic rule finish
  theme/                # ThemeProvider, useTheme, themeConstants
  components/<Name>/    # Name.jsx, Name.css, index.js, Name.test.jsx
    _shared/            # useDetentTravel, useReducedMotion, validateStationCount
playground/             # the live style guide (see "Seeing it")
vite-plugin.js          # watchDesignSystem() for local cross-repo HMR

Component tokens live with their components' concern — the slide-rule navs read --ds-rule-* from tokens/rule.css, whose defaults are expressions over the bg/font/bw families, so both navs re-skin under every theme with no per-theme work.


Releasing

npm publish requires a manual version bump in package.json first — there is no version script. prepublishOnly runs the build.