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

@dimesdesign/cookie-consent

v1.0.0-beta.1

Published

GDPR cookie consent for EU websites: Google Consent Mode v2, script and iframe gating, four consent categories, themeable through CSS tokens. Zero dependencies.

Readme

cookie-consent

GDPR / ePrivacy cookie consent for EU websites. Built by Georgi Tsonev for reuse across projects: one script, one stylesheet, everything else is configuration.

  • Google Consent Mode v2 (consent default / update) and GTM dataLayer events
  • Script and iframe gating with "accept to load" placeholders for embeds
  • Four categories by default (necessary · preferences · analytics · marketing), fully configurable, each with an optional cookie table
  • Reject all is as easy as Accept all. No pre-ticked boxes, no dark patterns, close = reject
  • Consent record with UUID, timestamp, version and method; optional server-side log (Cloudflare Worker + D1, see worker/)
  • Re-consent when the policy version changes; 12-month expiry by default
  • Three visual presets (quiet, editorial, industrial), light / dark / auto, every value a --cc-* token
  • Texts: DOM override → translations → built-in EN (BG included)
  • Zero dependencies, ~11 KB JS + 4 KB CSS gzipped, WCAG 2.2 AA (focus trap, role="dialog", keyboard, reduced motion)

Install

CDN (any site, Webflow included)

<!-- <head>, before GTM / gtag.js -->
<link rel="stylesheet" href="https://cookie-consent-ati.pages.dev/v1/cookie-consent.css">
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }
  gtag("consent", "default", {
    ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied",
    analytics_storage: "denied", functionality_storage: "granted",
    personalization_storage: "denied", security_storage: "granted",
    wait_for_update: 500
  });
  window.__cc_defaults = 1;
</script>

<!-- end of <body> -->
<script src="https://cookie-consent-ati.pages.dev/v1/cookie-consent.js"></script>
<script>
  CookieConsent.init({
    policyVersion: "2026-09-14",
    theme: "quiet",
    layout: "bar",
    links: { cookiePolicy: "/cookie-policy", privacyPolicy: "/privacy-policy" }
  });
</script>

The head snippet is only needed when GTM or gtag.js loads before the script (which is the usual case). If you skip it, the script sets the same defaults on init().

/v1/ receives non-breaking updates only. A breaking release lands in /v2/.

npm (Vite / bundlers)

npm i @dimesdesign/cookie-consent
import { init } from "@dimesdesign/cookie-consent";
import "@dimesdesign/cookie-consent/style.css";

init({ policyVersion: "2026-09-14" });

Types ship with the package; every option below is documented in Config.

Gating scripts and embeds

Anything that sets non-essential cookies must not run before consent. Change type to text/plain and tag the category:

<!-- GA4 direct -->
<script type="text/plain" data-cc="analytics" async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>
<script type="text/plain" data-cc="analytics">
  gtag("js", new Date()); gtag("config", "G-XXXX");
</script>

<!-- Embeds: src → data-cc-src. Blocked iframes get a placeholder with an "Accept and load" button. -->
<iframe data-cc="marketing" data-cc-src="https://www.youtube-nocookie.com/embed/ID" data-cc-provider="YouTube" width="16" height="9"></iframe>

<!-- Several categories: all must be granted -->
<script type="text/plain" data-cc="analytics,marketing" src="…"></script>

Elements added later (CMS lists, SPAs) are picked up by a MutationObserver.

With Google Tag Manager

Load GTM normally (it is consent-aware through Consent Mode). Every tag that sets cookies gets the built-in consent checks (Tag → Consent settings → require additional consent). On top of Consent Mode the script pushes:

| dataLayer push | When | |---|---| | { event: "cc_consent_update", cc_consent: { analytics: true, … }, cc_consent_id, cc_consent_method, cc_consent_version } | on init with stored consent, and on every change | | { event: "cc_consent_analytics" } (one per granted category) | same |

Use cc_consent_update + a Data Layer Variable cc_consent.analytics as a trigger condition, or the per-category events directly.

Categories and Consent Mode mapping

Defaults:

| id | required | Consent Mode v2 types | |---|---|---| | necessary | yes | security_storage, functionality_storage | | preferences | | personalization_storage | | analytics | | analytics_storage | | marketing | | ad_storage, ad_user_data, ad_personalization |

Override with categories: [...], adding a cookie table per category:

categories: [
  { id: "necessary", required: true, consentMode: ["security_storage", "functionality_storage"],
    cookies: [{ name: "cc_consent", provider: "example.com", purpose: "Remembers your cookie choice.", duration: "12 months" }] },
  { id: "analytics", consentMode: ["analytics_storage"],
    cookies: [
      { name: "_ga",   provider: "Google Analytics", purpose: "Distinguishes visitors.", duration: "2 years" },
      { name: "_ga_*", provider: "Google Analytics", purpose: "Session state.", duration: "2 years" },
    ] },
]

Cookies listed in a category are deleted when that category is rejected or withdrawn (autoClear, on by default; wildcards supported).

Texts and languages

Resolution per key: DOM override → translations[lang] → built-in locale (EN, BG). Language comes from lang in config, else <html lang>, else fallbackLang.

translations: {
  en: { banner: { title: "Cookies on elica-pro.com" } },
  bg: { banner: { title: "Бисквитки на elica-pro.com" } },
}

For Webflow Localization, put the copy in the page as hidden elements and let Webflow translate them:

<div hidden data-cc-i18n>
  <div data-cc-text="banner.title">This site uses cookies</div>
  <div data-cc-text="banner.description">Strictly necessary cookies keep the site working …</div>
  <a data-cc-text="links.cookiePolicy" href="/cookie-policy">Cookie Policy</a>
</div>

Keys mirror the Texts type: banner.*, preferences.*, categories.<id>.title|description, links.*, placeholder.*, badge.label. Description fields accept HTML. Link elements also supply the href.

Layout, lock and themes

init({
  layout: "bar",               // "bar" | "card" | "modal" (default: bar, bottom)
  position: "bottom",          // bar: top|bottom · card: top-left|top-right|bottom-left|bottom-right · modal: center
  lock: false,                 // backdrop + scroll lock + focus trap until a choice is made (default true for modal)
  theme: "quiet",              // "quiet" | "editorial" | "industrial" | your own data-cc-theme value
  colorScheme: "light",        // "light" | "dark" | "auto"
  badge: false,                // floating reopen button after a choice
  showCloseButton: true,       // X on the banner (non-lock only). Closing = reject.
  tokens: { "--cc-color-accent": "#F58420", "--cc-font-family-heading": "'Exo 2', sans-serif" },
})

Or override tokens in your own CSS:

.cc { --cc-color-accent: #F58420; --cc-color-accent-hover: #D9741C; --cc-radius: 0.25em; }

All tokens (with defaults) are at the top of src/styles/cookie-consent.css: typography (--cc-font-family, --cc-title-size …), colours (--cc-color-bg/fg/muted/border/accent/backdrop …), shape (--cc-radius, --cc-shadow), layout (--cc-offset, --cc-card-width, --cc-z …) and motion (--cc-duration: 0.6s, --cc-ease). Sizes are in em, so the banner follows the host page's font size (--cc-font-size: 1em).

Reopen link anywhere on the site:

<a href="#" data-cc="open">Cookie settings</a>

API

import * as CookieConsent from "@dimesdesign/cookie-consent"; // or window.CookieConsent on the CDN build

CookieConsent.init(config);
CookieConsent.show();                 // banner
CookieConsent.hide();
CookieConsent.showPreferences();      // settings dialog
CookieConsent.hidePreferences();
CookieConsent.acceptAll();
CookieConsent.rejectAll();
CookieConsent.accept(["marketing"]);  // grant on top of current choice
CookieConsent.setCategories({ analytics: true });
CookieConsent.getConsent();           // ConsentRecord | null
CookieConsent.isAllowed("analytics"); // boolean
CookieConsent.getCategories();        // ["necessary", …]
CookieConsent.setLang("bg");          // re-render in another language
CookieConsent.reset();                // forget the choice, show the banner again
CookieConsent.destroy();
CookieConsent.on("change", ({ consent, previous }) => {});   // also: init, show, hide, preferences:show, preferences:hide
window.addEventListener("cc:change", (e) => e.detail.consent); // same events on window

Consent record:

{ "id": "6d0c…", "ts": "2026-09-14T10:02:11.000Z", "expires": "2027-09-14T10:02:11.000Z",
  "version": "2026-09-14", "lang": "en", "method": "custom",
  "categories": { "necessary": true, "preferences": false, "analytics": true, "marketing": false } }

Stored in the cc_consent cookie (SameSite=Lax, Secure on https, expiresDays = 365) and mirrored in localStorage for Safari's 7-day cookie cap. Set cookie.domain to share consent across subdomains.

Proof of consent (optional)

init({ logEndpoint: "https://cookie-consent-log.<you>.workers.dev/log", siteKey: "elica-pro" })

Every choice is POSTed to the Worker in worker/ and stored in D1 with no IP address or user agent. Setup and export commands are in worker/README.md.

Behaviour reference

| | | |---|---| | First visit | banner shows (unless the path is in excludePaths), nothing non-essential loads | | Accept all / Reject all | equal prominence; stored, Consent Mode updated, gated elements activated | | X / Escape (non-lock) | stored as reject (closeAction: "reject", CNIL position); set "dismiss" to only hide | | Settings | per-category switches, necessary locked on; Save / Accept all / Reject all | | Stored choice expires | after expiresDays; banner asks again | | policyVersion changes | stored choice discarded; banner asks again | | Withdrawal | Consent Mode set to denied, listed cookies deleted; reloadOnWithdraw: true reloads so running scripts stop | | GPC signal | ignored by default (respectGPC: false); when true, treated as reject-all without a banner | | Reduced motion | transitions off |

Development

npm i
npm run dev          # demo / design review on http://localhost:5180
npm test             # Vitest (jsdom)
npm run test:e2e     # Playwright (desktop + iPhone 13)
npm run build        # dist/ (ESM + d.ts + css) and dist/cdn/v1/ (IIFE)
npm run deploy:cdn   # Cloudflare Pages project "cookie-consent"
node scripts/shots.mjs   # screenshot matrix into docs/shots/

Docs: docs/gdpr-checklist.md · docs/webflow-integration.md · worker/README.md

MIT © Georgi Tsonev