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

consent-ui

v0.1.0

Published

A tiny, tree-shakeable React component library with CSS Modules and CSS variable theming.

Readme

consent-ui

A small, tree-shakeable React component library. Ships Button and Card, styled with CSS Modules and themed entirely through --consent-ui-* CSS custom properties.

  • No CSS reset. The package never restyles your elements. Each component imports only its own scoped stylesheet.
  • SSR and RSC safe. Verified against react-dom/server; works in the Next.js App Router.
  • React is a peer dependency. It is never bundled.
  • Tree-shakeable. ESM + CJS, preserveModules, and per-component subpath exports.

A bundler is required. Components import their own CSS, so the package cannot be loaded by plain Node without a build step. Next.js, Vite, webpack, Remix, Parcel and friends all handle this out of the box.

Install

pnpm add consent-ui
# npm install consent-ui / yarn add consent-ui

React 18 or 19 is required as a peer dependency.

Usage

import { Button, Card } from 'consent-ui';

export function CookieBanner() {
  return (
    <Card
      title="Cookie preferences"
      description="We use cookies to improve the site."
      footer={
        <>
          <Button variant="primary">Accept all</Button>
          <Button variant="ghost">Reject all</Button>
        </>
      }
    >
      Choose which categories you allow. You can change this at any time.
    </Card>
  );
}

Component CSS is imported by the components themselves — there is nothing extra to import for them to look right.

Individual imports

Every component also has its own entry point:

import { Button } from 'consent-ui/button';
import { Card } from 'consent-ui/card';

How tree-shaking behaves

The package is built with preserveModules, so every component is its own module, and it declares "sideEffects": ["**/*.css"]. Unused component JavaScript is always removed, whichever import style you use.

Stylesheets differ slightly by bundler, because CSS imports are side effects:

| Bundler | import { Button } from 'consent-ui' | import { Button } from 'consent-ui/button' | | ------------------------ | ------------------------------------- | -------------------------------------------- | | Vite / Rollup | Only Button.css | Only Button.css | | Next.js / webpack | All component CSS | Only Button.css |

Webpack collects CSS across the whole module graph before tree-shaking, so the barrel pulls in every component's stylesheet even though the unused JavaScript is dropped. If you use Next.js and want the smallest possible CSS, import from the subpaths. The difference is small — the full stylesheet for this library is a few kilobytes — but the option is there.

Next.js

Works out of the box in both the App Router and Pages Router — no transpilePackages needed.

// app/page.tsx — a Server Component
import { Card, Button } from 'consent-ui';

export default function Page() {
  return <Card title="Cookies" footer={<Button>Accept all</Button>}>…</Card>;
}

Button is marked 'use client' because it handles events; Card is not, so it renders as a Server Component. You can drop either into a server or client tree without thinking about it.

Class names are generated at build time and are identical on the server and client, so there are no hydration mismatches.

Theming

All styling is driven by CSS custom properties. Set them on any ancestor to retheme that subtree — you never need to override a class name:

.checkout {
  --consent-ui-color-accent: #7c3aed;
  --consent-ui-radius-md: 12px;
}

Every component ships fallback values inline, so they look correct without any extra setup.

The token stylesheet (optional)

If you want to override tokens globally, import the base stylesheet once at the root of your app:

// app/layout.tsx or pages/_app.tsx
import 'consent-ui/tokens.css';

This file only declares custom properties on :root — it contains no reset and no element selectors, so it cannot change how your own markup looks.

Once imported, you can opt into the bundled dark theme:

<html data-consent-ui-theme="dark">
<!-- or follow the OS setting -->
<html data-consent-ui-theme="auto">

Available tokens

| Group | Tokens | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Surface | --consent-ui-color-surface, --consent-ui-color-surface-muted, --consent-ui-color-border | | Text | --consent-ui-color-text, --consent-ui-color-text-muted, --consent-ui-color-text-on-accent | | Intent | --consent-ui-color-accent{,-hover,-active}, --consent-ui-color-danger{,-hover,-active} | | Focus | --consent-ui-color-focus-ring, --consent-ui-focus-ring-width, --consent-ui-focus-ring-offset | | Radius | --consent-ui-radius-sm (4px), -md (8px), -lg (12px), -full | | Spacing | --consent-ui-space-1--consent-ui-space-6 | | Typography | --consent-ui-font-family, --consent-ui-font-size-{sm,md,lg,xl}, --consent-ui-font-weight-{regular,medium,bold}, --consent-ui-line-height | | Elevation | --consent-ui-shadow-sm, --consent-ui-shadow-md | | Motion | --consent-ui-transition-fast | | Misc | --consent-ui-disabled-opacity |

Per-instance overrides

Because tokens cascade, you can theme a single component inline:

<Button style={{ '--consent-ui-color-accent': '#059669' } as React.CSSProperties}>
  Accept
</Button>

You can also pass className — it is merged with the component's own classes, never replaced.

API

Button

Extends all native <button> props.

| Prop | Type | Default | Description | | -------------- | -------------------------------------------------- | ----------- | --------------------------------------------------------------- | | variant | 'primary' \| 'secondary' \| 'ghost' \| 'danger' | 'primary' | Visual style. | | size | 'sm' \| 'md' \| 'lg' | 'md' | Height and horizontal padding. | | fullWidth | boolean | false | Stretch to fill the container. | | loading | boolean | false | Show a spinner and block clicks, while staying focusable. | | loadingLabel | string | 'Loading' | Text announced while loading. | | startIcon | ReactNode | — | Rendered before the label. | | endIcon | ReactNode | — | Rendered after the label. |

type defaults to "button" so it never submits a form by accident. While loading, the button sets aria-busy and aria-disabled rather than the disabled attribute, so focus is not lost and screen readers announce the state change.

Card

Extends all native <div> props except title, which is widened to ReactNode.

| Prop | Type | Default | Description | | -------------- | --------------------------------------------- | ------------ | ------------------------------------------------------ | | variant | 'elevated' \| 'outlined' \| 'filled' | 'outlined' | Visual style of the surface. | | padding | 'none' \| 'sm' \| 'md' \| 'lg' | 'md' | Inner spacing for all slots. | | interactive | boolean | false | Hover and focus-within affordances. | | title | ReactNode | — | Heading rendered above the content. | | description | ReactNode | — | Supporting text under the title. | | headingLevel | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 3 | Heading level for title, so it fits your page outline. | | media | ReactNode | — | Full-bleed slot above the header. | | footer | ReactNode | — | Slot at the bottom, typically actions. |

Both components forward refs and spread unknown props onto the underlying element.

Development

pnpm install
pnpm dev              # Storybook on http://localhost:6006
pnpm test             # Vitest + React Testing Library (incl. SSR tests)
pnpm typecheck
pnpm build            # ESM + CJS + .d.ts/.d.cts into dist/
pnpm lint:package     # publint + are-the-types-wrong

Adding a component

  1. Create src/components/MyThing/ with MyThing.tsx, MyThing.module.css, index.ts, a test and a story.
  2. Add 'use client' at the top of the .tsx only if it uses hooks, events or browser APIs.
  3. Re-export it from src/index.ts.
  4. Add an entry to build.lib.entry in vite.config.ts and a matching subpath in exports and typesVersions in package.json.

Keep CSS scoped to classes — no element or global selectors, so the package can never reset a consumer's styles.

Releasing

Releases run through Changesets.

  1. Add a changeset with your PR:

    pnpm changeset
  2. Merge to main. CI opens a Version Packages PR that applies the version bump and updates CHANGELOG.md.

  3. Merge that PR. CI publishes to npm with provenance.

This requires an NPM_TOKEN repository secret with publish rights. The package is published publicly (publishConfig.access: "public").

License

MIT