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

@hypercerts-org/ui-react

v0.2.0

Published

Token-based React component library for Hypercerts. Brand-blind primitives; a theme supplies the values.

Readme

@hypercerts-org/ui-react

Token-based React component library. The primitives are brand-blind: they reference a closed set of semantic utilities and nothing else, and a theme decides what those mean. Re-branding is a token file, not a fork.

Three themes ship: Hypercerts (the default), Certified, and a template for a white-label partner.

Installing it

npm install @hypercerts-org/ui-react

To develop against this repository and a consuming app at the same time, a local path dependency is easier than republishing — "@hypercerts-org/ui-react": "file:../hypercerts-design/packages/react" — or npm pack here and install the tarball.

There is a Svelte package alongside this one, @hypercerts-org/ui-svelte, with the same components and the same props. The two are compared on every pull request by scripts/parity.mjs, and again before either can publish.

Upgrading from 0.1.0

0.2.0 changes the API. Pre-1.0 the minor is the breaking position, so a ^0.1.0 range will not pull it — you have to ask for it, which is deliberate.

  • Popover split into Popover and Menu. The old Popover hardcoded role="menu", so it was a dropdown menu under the wrong name and there was no way to float arbitrary content. Menu is a list of actions with the menu keyboard; Popover is a role="dialog" panel that intercepts nothing, so a form or a date picker inside it works. PopoverItem is now MenuItem.
  • Renamed for parity with the Svelte package: aria-label → label on InfoTooltip, SearchInput and MenuButton. InfoTooltip's old label (the visible trigger text) is now triggerLabel — the two packages had a prop called label meaning different things.
  • Removed: SignInDialog and FeedbackDialog (compositions that encoded a product decision — rebuild either from ResponsiveDialog + Field + Button), TabPanelTransition (now <Tabs transition>), and TableCaption (Table renders its own from the required caption prop).
  • Added: twenty-one primitives, including Table, Calendar, DatePicker, Slider, Accordion, Breadcrumb and FileDropzone. See docs/maearth-intake.md.

Install and use

The library does not bundle the brand system — it declares a dependency on it, so that theme/hypercerts.css and assets/fonts/ stay the single source of truth for brand values instead of being copied in here. Import both, in this order:

/* your app's stylesheet — or a plain <link>, no build step required */
@import "@hypercerts-org/ui-react/styles.css";

That is the whole requirement. Tailwind is not needed: the utilities are compiled into that file, and the brand tokens are resolved into it at build time. It defines nothing outside the components it renders — no reset, no body rule, no restyled headings.

If you do want the document styled to match — page background, headings in the display face, Tailwind's reset — that is a second, opt-in import:

@import "@hypercerts-org/ui-react/styles.css";
@import "@hypercerts-org/ui-react/base.css";   /* optional */

Fonts are not included and cannot be. Switzer's licence prohibits redistribution (see assets/fonts/README.md), so the package ships font stacks, never files. Self-host Instrument Serif and Switzer yourself — both are free — and the components pick them up. Without them everything renders correctly in a fallback face.

Inside this repo, one import does both:

@import "../../src/styles/with-brand.css";

That file is repo-internal — it reaches up to theme/hypercerts.css, which sits outside the package, so it is deliberately not a package entry point. From another project, vendor theme/ and assets/ as the root README describes and import your own copy.

Then pick a theme by setting one attribute on the root element:

<html data-hc-theme="hypercerts">
import { Button, Card, Heading, Eyebrow, Badge } from "@hypercerts-org/ui-react";

<Card>
  <Eyebrow>Round 21</Eyebrow>
  <Heading level={3} size="heading">Restoring the Kinabatangan floodplain</Heading>
  <Badge variant="success">Verified</Badge>
  <Button>Endorse</Button>
</Card>;

Forgetting the brand import fails loudly — every value resolves to nothing and the page renders unstyled. That is deliberate; a silent fallback to system-default styling is how a half-configured app ships looking almost right.

The one thing to understand

Two themes here contradict each other on nearly everything: Hypercerts is 12px-radius, cream-and-serif, light-only, one accent; Certified is 2px-radius, monochrome, dark-mode-required. Neither set of values lives in a component.

docs/tokens.md       the closed vocabulary, and how it maps to both systems
docs/decisions.md    every conflict between them, and how it resolved
docs/white-labeling.md   what a partner changes, and what they may not

If you are about to write a hex value, a font name, a px radius or a dark: variant in src/components/, the answer is a token — and npm run lint will say so before review does.

What's here

Foundations — Heading (with the italic turn), Eyebrow, Text, Prose. The first two carry brand rules 1 and 2, which are content conventions no token can express. Prose styles rendered Markdown, where there are no elements to put classes on.

Primitives — Button, Badge, Card, Avatar, Skeleton, LoadingSpinner, Tooltip, InfoTooltip, ErrorMessage, EmptyState, Separator, Progress. InfoTooltip is the one to reach for when the content is the only copy: it opens on tap as well as hover, and is announced. Tooltip is CSS-only and aria-hidden, for a hint that repeats something already on screen.

Forms — Field, Input, Textarea, Select, Checkbox, Radio, Switch, NumberInput, SearchInput, InputGroup (+ InputGroupAddon, InputGroupInput), Slider, CharacterCount, FileDropzone, Calendar, DatePicker. DatePicker's text field is the control and the calendar is an accelerator: typing 2026-09-15 works without ever opening the popover, which matters for any date more than a month out. Checkbox, Radio and Switch render their own labels. The bare controls go through Field, which wires label, description and error to the control — so nothing can ship without an accessible name.

Navigation — Tabs / TabList / Tab / TabPanel, SegmentedControl, Pagination, Breadcrumb, Stepper, MenuButton, LoadMoreSentinel. The direction-aware panel slide is <Tabs transition>, not a separate component — its only correct usage was beside Tabs, and the direction needs the tab order Tabs already holds.

Layout and data — Container, ScrollArea, Carousel +CarouselItem, Reveal, Accordion + AccordionItem, ExpandableText, and the Table family (Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell).

Overlays — Dialog, ConfirmDialog, ConfirmByTypingDialog, ResponsiveDialog, Drawer, BottomSheet, Menu (+ MenuTrigger, MenuContent, MenuItem), Popover (+ PopoverTrigger, PopoverContent), Banner, ToastProvider + useToast. Every overlay traps focus while open, locks page scroll, closes on Escape, and restores focus to whatever opened it.

Menu and Popover are different patterns and the difference matters. Menu is a list of actions: role="menu", one tab stop, arrow keys and typeahead between items. Popover is a panel of arbitrary content: role="dialog", and nothing intercepted, so a form or a date picker inside it actually works. Putting a form in a menu is invalid ARIA and a keyboard trap — the menu's arrow keys swallow the ones the inputs need.

Data entry and lists — Combobox (typeahead: state machine and ARIA, no data layer) + ComboboxOption, LoadMoreSentinel, SettingRow, IdentityRow, OwnerByline, CopyableId, ThemeToggle.

There is no SignInDialog or FeedbackDialog, and that is deliberate. Both existed and were removed: they are compositions of the primitives above that encode a product decision, and a library shipping a sign-in shell is telling every consumer what their sign-in looks like. ui/sidebar was left out of the Ma Earth intake on exactly that argument, so keeping these would have been inconsistent. Either is about twenty lines of ResponsiveDialog + Field + Button in an app.

Routing — UIProvider takes a link component so Button href=…, Pagination and the row primitives render real anchors that navigate through your router. Without it they fall back to a plain <a>.

Hooks — useFocusTrap, useScrollLock, useDismiss, useMounted, useIsDesktop, useCanHover, useReducedMotion.

Where the newer half came from

Twenty of the components above were derived from MaEarth/maearth-app — read for behaviour and API, then re-implemented against the token vocabulary rather than copied. That app's own ui/ folder is shadcn on Radix with brand values hardcoded into it; taking it would have brought eighteen runtime dependencies into a package that has one, and ended the Svelte package's parity. What was worth having was the layer above it, where components had been written under real product pressure and carried comments explaining the bug they existed to fix.

docs/maearth-intake.md is the record: what was taken, what was already covered, what was deliberately left, and why. It is worth reading before adding to that set.

The date arithmetic behind both lives in packages/shared/date/calendar.ts and is exported (toISODate, fromISODate, addDays, addMonths, …). It is all local-time and day-by-day rather than by milliseconds — toISOString() reports yesterday for anyone west of Greenwich, and adding 86,400,000ms lands on the wrong day across a DST boundary. Both are in the tests.

Still not built: the funding-platform patterns — contribution input, funding progress, project card, contributor list, round status. These are compositions of the primitives above rather than gaps in them, and they want a real consumer to shape them.

Scripts

| | | |---|---| | npm run storybook | the component gallery, with a live theme switcher | | npm run build | the library — dist/index.js + dist/hypercerts-ui.css | | npm run build-storybook | the static gallery | | npm run lint | ESLint, including the no-literal-design-values rule | | npm run typecheck | tsc --noEmit | | npm test | vitest — behaviour and accessibility contracts | | npm run test:a11y | axe over every story in a real browser (needs Storybook running) |

What the tests cover

npm test is not a smoke test. It pins the behaviour that is expensive to get right and silent when it breaks: the focus trap wrapping in both directions, the roving tabindex, arrow keys skipping a disabled tab, End landing on the last enabled tab, scroll lock applying and releasing, focus returning to the trigger, overlays actually portalling, popover dismissal by outside press / Escape / selection, and every status badge carrying its icon.

Each of those was mutation-checked — the assertion was confirmed to fail when the behaviour is removed. One of them didn't at first: "focus is still inside the dialog" passes even with the trap gone, so it now asserts the exact element focus lands on.

npm run test:a11y runs axe in a real browser, which is the only place colour contrast can be checked at all — jsdom has no layout. That is how ui-grey #999999 was caught at 2.85:1.

Hosting it, and catching visual drift

Storybook runs locally, but it is also built as a static site on every PR — the point of a component gallery is a link, not a clone. npm run build-storybook produces storybook-static/, which is plain HTML and can be hosted anywhere.

The published gallery lives at https://6a9ffc58071e7b7e9f862cfb-jplneoagws.chromatic.com/ — that link is the one to send someone rather than asking them to clone the repo.

It is published by Chromatic, which hosts the gallery and screenshots every story on every PR, so an unintended visual change shows up as a diff to accept or reject rather than as something noticed months later. With three contradictory themes in play, a single token edit can restyle forty components at once; this is what makes that visible.

Every story is snapshotted in three modes — hypercerts, certified and certified-dark — configured in .storybook/preview.tsx. Snapshots cost stories x modes, roughly 195 a build. If that outgrows the plan, drop certified-dark first: it shares a token vocabulary with certified and only the values differ.

Setup is done — the project exists and CHROMATIC_PROJECT_TOKEN is set as a repository secret. The workflow skips itself when that secret is absent rather than failing, so a clone or a fork does not go red for a secret it was never going to have.

Both triggers are path-filtered so an unrelated commit does not spend snapshots, which also means an empty commit triggers nothing. Run it by hand with gh workflow run chromatic.

To run it by hand: CHROMATIC_PROJECT_TOKEN=… npm run build-storybook && npm run chromatic.

Publishing

Tagged releases only:

npm version patch          # or minor / major
git push --follow-tags

A ui-react-v* tag runs .github/workflows/release.yml, which checks the tag matches package.json, then publishes. prepublishOnly runs lint, typecheck, tests and the build first, so a broken publish has to get past all of them. Needs an NPM_TOKEN secret with rights on the @hypercerts-org scope; without it the job stops rather than failing.

Without npm provenance, which needs a public source repository — this one is private, and cannot be made public while assets/fonts/Switzer-Variable.woff2 is in it, because the ITF Free Font License does not permit redistribution. The workflow says what to change if the font ever moves out.

The package name carries -react deliberately, so that the Svelte implementation is a peer rather than an afterthought and neither becomes "the" library by accident. @hypercerts-org/ui is left unclaimed for the same reason.

Where this sits

packages/react/ and packages/svelte/ are the only parts of this repository with a build step, over the shared source in packages/shared/. The root stays buildless on purpose: brand/, theme/, tokens/, assets/ and examples/ are copy-two-directories-and-go, and every non-React deliverable made from this system depends on that. Do not dilute it.