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

@snowpact/snowcookie

v0.2.0

Published

Minimal, CNIL-friendly cookie-consent for React: one category, accept/refuse, versioned 6-month persistence, styled banner with standalone CSS

Readme

@snowpact/snowcookie

Minimal, CNIL-friendly cookie-consent for React — one consent category, accept/refuse, re-openable, versioned 6-month persistence. No runtime dependency, no Tailwind: the banner ships its own standalone CSS and inherits your font.

Live Demo

What it does

  • Stores the choice (acceptance and refusal) in a first-party cookie for 182 days (~6 months, the CNIL ceiling before re-asking).
  • version bump → every visitor is re-prompted (use it when your tracker roster changes).
  • Malformed/unknown cookie → treated as pending, never throws.
  • SSR-safe: server HTML and first client render both see pending (no banner, no tracker), the cookie is read after mount — no hydration mismatch, no banner flash for returning visitors.
  • Non-blocking banner (role="dialog" aria-modal="false", no overlay, no focus trap), accept and refuse rendered with equal prominence — both CNIL requirements.
  • reopen() re-prompts without dropping the stored choice: the previous consent record stays valid until the user actually decides again.

Install

pnpm add @snowpact/snowcookie

Peer dependency: react >= 18.

Usage

import { SnowCookie, SnowCookieProvider, useSnowCookie } from '@snowpact/snowcookie';
import '@snowpact/snowcookie/styles.css';

CSS import order matters: import styles.css after your global/reset stylesheet (Tailwind preflight, normalize…). Resets target [type='button'] with the same specificity as the banner classes — imported first, the package CSS would lose the button styling coin-flip.

Wrap your whole app shell with the provider — including the footer if it hosts the "Manage cookies" button (reopen needs the context):

<SnowCookieProvider version={1} cookieName="my_app_cookie_consent">
  <App />
</SnowCookieProvider>

Gate your trackers and mount the banner anywhere below the provider:

const { isGranted } = useSnowCookie();

{isGranted && <MyAnalyticsScript />}
<SnowCookie
  title="Cookies et mesure d'audience"
  description="…"
  acceptLabel="Accepter"
  refuseLabel="Refuser"
  policyHref="/privacy"
  policyLabel="En savoir plus"
  onRefuse={handleRefuse}
/>

Labels are plain props: i18n stays in the host app, the package hardcodes no user-facing text.

"Manage cookies" footer link → re-prompt:

const { reopen } = useSnowCookie();
<button type="button" onClick={reopen}>Gérer les cookies</button>

Refusing after trackers already ran (read this)

Two things every consumer needs, learned the hard way:

  1. A tracker that already loaded keeps tracking until the page dies. Unmounting the script tag is not enough — reload the page when a refusal follows an acceptance in the same session.
  2. Trackers can rewrite their cookies between your click-time purge and the unload (GA4 does). Re-purge on every visit where the status is denied.

The complete recipe:

const TRACKER_COOKIE_PREFIXES = ['_ga', '_gid', '_clck', '_clsk', '_pk_'];

const { isGranted, status } = useSnowCookie();

// Latch: did a tracker run this session?
const wasGranted = useRef(false);
useEffect(() => {
  if (isGranted) wasGranted.current = true;
}, [isGranted]);

// Catch cookies rewritten after the click-time purge (GA4 notably).
useEffect(() => {
  if (status === 'denied') purgeCookies(TRACKER_COOKIE_PREFIXES);
}, [status]);

const handleRefuse = () => {
  purgeCookies(TRACKER_COOKIE_PREFIXES);
  if (wasGranted.current) window.location.reload();
};

purgeCookies is best-effort by design: it expires each matching cookie against the hostname and every parent domain (the only way to delete a _ga set on .example.com).

API

  • readConsent(cookieName, version) / writeConsent(cookieName, version, 'granted' | 'denied') / clearConsent(cookieName) — pure cookie semantics, usable without React.
  • purgeCookies(prefixes: string[]) — best-effort deletion of tracker cookies by name prefix.
  • SnowCookieProvider / useSnowCookie() → { status, isGranted, isPending, accept, refuse, reopen }.
  • <SnowCookie /> — the banner; renders null unless the choice is pending. Props: description, acceptLabel, refuseLabel, title?, policyHref?, policyLabel?, onAccept?, onRefuse?, className?.

Theming

Three CSS custom properties, overridable from any scope (defaults live in var() fallbacks, so :root just works):

:root {
  --snow-cookie-accent: #140b7c; /* buttons, link, icon — default: neutral #1f2937 */
  --snow-cookie-accent-fg: #ffffff; /* accept button text */
  --snow-cookie-z: 60;
}

The banner inherits the host font (font: inherit — set your font on body). Anything else (background, radius, spacing…) is overridable with plain CSS on the flat .snow-cookie-* classes — import your override after styles.css.

Next.js note

The package ships built ESM/CJS + types: no transpilePackages needed. Import the CSS from _app (Pages Router) or the root layout (App Router).

Browser support & limits

Plain document.cookie with max-age + expires + SameSite=Lax — every modern browser. Known limit shared by all client-side consent banners: Safari ITP caps JS-written cookies at ~7 days, so Safari users may be re-prompted weekly (legally harmless — worst case you re-ask).

Development

pnpm install
pnpm test
pnpm typecheck
pnpm build          # dist/: index.js (ESM) + index.cjs + index.d.ts + styles.css

Release (from main, clean tree, npm auth):

./scripts/release.sh patch|minor|major