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-control

v2.1.1

Published

Consent external sources, cookies, analytics,... with user-centered design

Downloads

527

Readme

Consent Control

npm version

Consent Control gives the website visitor full control before loading various assets, analytic tools or external services like Embedded Videos, Maps,... Without too much distraction. And fully customizable.

Note: There are Browser Plugins like i-dont-care-about-cookies.eu that hide a consent banner like this.

See demo

Which package do I need?

consent-control is the framework-agnostic runtime (this package). For server-rendered Laravel / Filament apps, install a wrapper instead — it vendors this runtime and adds Blade components, config and translations, so you don't wire the JS yourself:

| Environment | Install | |---|---| | Plain HTML, WordPress, Vite, React, any JS | consent-control (this package) — see below | | Laravel (Blade) | laravel-consent-control | | Laravel + Filament | filament-consent-control |

Usage

Install

npm i consent-control

Include CSS & Javascript

<!-- Obsolete if you use Bootstrap -->
<link href="dist/consentcontrol.bootstrap.css" rel="stylesheet" type="text/css" />
<!-- Main Stylsheet -->
<link href="dist/consentcontrol.main.css" rel="stylesheet" type="text/css" />

<script src="dist/bundle.min.js"></script>
import { ConsentControl, loadScript } from "consent-control"
// Obsolete if you use Bootstrap
@import "~consent-control/dist/consentcontrol.bootstrap.css";
// Main Stylesheet
@import "~consent-control/dist/consentcontrol.main.css";

Fire

ConsentControl.init({
   switches: {
      necessary: {
         disabled: true,
         checked: true,
         label: 'Notwendige',
         description: 'Stellt die Funktionalität der Website sicher.',
         childs: [
            {
               label: 'Seiten-Einstellungen',
               description: `Speichert Ihre Einstellungen in diesem Banner, Cookie
            <strong>consentbanner</strong> Speicherdauer 1 Jahr`,
            },
            {
               label: 'Schriftarten',
               description:
                  'Lädt die Schriftart "XY" von externen Servern von Adobe Fonts / Typekit',
            },
         ],
         callback: function() {
            loadScript('https://use.typekit.net/xyz.js', () => {
               try {
                  Typekit.load({async: true});
               } catch (e) {}
            })
         }
      },
      analytics: {
         label: 'Analytics',
         description:
            'Erlauben Sie dem Website-Betreiber, das Angebot auf dieser Webseite zu bewerten und zu verbessern.',
         childs: [
            {
               label: 'Google Tag Manager',
               description: `'UA-123456789-1, Cookie <strong>_ga</strong> Speicherdauer 2 Jahre`,
            },
         ],
         callback: function() {
            var gtm = document.createElement('script');
            gtm.type = 'text/javascript';
            gtm.async = true;
            gtm.src = 'https://www.googletagmanager.com/gtag/js?id=UA-123456789-1';
            var s = document.getElementsByTagName('script')[0];
            s.parentNode.insertBefore(gtm, s);
            window.dataLayer = window.dataLayer || [];
         
            function gtag() {
            dataLayer.push(arguments);
            }
            gtag('js', new Date());
         
            gtag('config', 'UA-123456789-1');
         }
      },
      functional: {
         label: 'Funktionell',
         description: 'Funktionen für die Darstellung der Inhalte.',
         childs: [
            {
               label: 'Google Maps',
               description:
                  'Stellt eine Karte mit Routenbeschreibung zur Verfügung und lädt diese von externen Servern von Google.',
            },
         ],
         callback: function() {
            setupMap()
         }
      }
   }
})

Reopen the banner

Visitors must be able to change their choice later (GDPR). Place a button with the consent-control--open class anywhere — typically on the privacy policy page. The runtime binds every such element on init() and reopens the banner with the settings expanded:

<button type="button" class="consent-control--open">Cookie settings</button>

Theming

The self-rendered banner reads CSS variables scoped to #consent-control-banner. The accent auto-adopts your site's primary colour when it exposes one (Tailwind --color-primary or Bootstrap --bs-primary); override any variable to brand it:

#consent-control-banner {
   --cc-primary: #b91c1c;            /* accent: checked switches, links */
   --cc-on-primary: #fff;
   --cc-bg: #fff;                    /* banner surface */
   --cc-text: #1f2937;               /* body text */
   --cc-muted: #6b7280;              /* secondary text */
   --cc-border: rgba(0, 0, 0, .12);  /* dividers */
   --cc-section: rgba(0, 0, 0, .035);/* switches section background */
}

Buttons are deliberately left unstyled so they inherit your site's button styling — give them your own classes via the template.footer/template.rejectButton options if needed.

Optional: "Reject all" button

By default the OK button already saves only the pre-checked (minimal) selection, so it acts as "reject all" as long as your default config leaves optional switches unchecked. If you pre-check optional switches, you can enable an explicit, opt-in reject button:

ConsentControl.init({
   rejectButton: true, // adds an "Alle ablehnen" button next to OK
   switches: { /* ... */ }
})

It saves only the locked necessary categories (disabled: true) and rejects everything else. Customise the label via template.strings.noneButtonLabel and the markup via template.rejectButton.

Declarative services (config-driven)

Instead of a callback function you can declare services as data — ideal when the config comes from a CMS / config file (PHP, WordPress, …) and cannot hold JS closures:

ConsentControl.init({
   categories: {            // `switches` still works as an alias
      analytics: {
         label: 'Analytics',
         scripts: [
            { src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXX', async: true },
         ],
         inlineScript: "window.dataLayer = window.dataLayer || []; /* … */",
      },
   },
})

scripts are injected once on consent (deduplicated by src); inlineScript runs once inside a try/catch. callback keeps working and can be combined with both.

Server-rendered markup (Laravel, WordPress, any framework)

ConsentControl.init() binds to an existing #consent-control-banner if present, so you can render the banner server-side and let the runtime only wire behaviour (no client re-render, no flash). Provide these selectors:

| Selector | Role | |---|---| | #consent-control-banner | Container. Start with classes hide is-collapsed so it stays hidden when a cookie already exists. | | .switchesinput[value="{key}"] | One checkbox per category (disabled/checked as needed). | | #consent-control--submit | Save selected ("OK"). | | #consent-control--submit-all | Allow all. | | #consent-control--submit-none | Reject all (optional, opt-in). | | .consent-control--open / --close / --reset | Reopen / collapse / delete all cookies. | | .collapsed-only / .uncollapsed-only | Visibility helpers. |

When the markup already exists, the matching template.* strings are ignored for those parts. The same applies to ConsentMessage (.consent-message--wrapper, iframe[data-src], button.confirm).

Consent updated event

Whenever the consent state is applied, a consent-updated event is dispatched on window:

window.addEventListener('consent-updated', (e) => {
   console.log('granted:', e.detail.consents) // e.g. ['necessary','analytics']
})

Consent versioning

Force a fresh opt-in when your categories or privacy policy change by setting a version. When the version stored in the cookie differs, existing consent is cleared and the banner re-appears:

ConsentControl.init({
   version: 2,        // bump whenever consent must be renewed
   categories: { /* … */ },
})

A companion cookie {cookieName}-v tracks the granted version. Leave version unset (default) to disable versioning.

Block your own <script> tags

Mark any script as consent-gated with type="text/plain" + data-consent. It stays inert until the matching category is granted, then it is activated automatically (works for external and inline scripts):

<script type="text/plain" data-consent="analytics"
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

<script type="text/plain" data-consent="analytics">
   window.dataLayer = window.dataLayer || [];
   gtag('config', 'G-XXXX');
</script>

Check for consent

import { getConsentControlCookie } from "consent-control"

   if (getConsentControlCookie('functional')) {
      setupMap()
   }

Show Consent Message for iframes

   const iframes = document.querySelectorAll('iframe[data-src][data-src-name="Vimeo"]')
   
   iframes.forEach((e) => {
      ConsentMessage.new(
         'functional',
         e,
         {
            template: {
               main: `<div class="consent-message"><button class="confirm play-button"></button><p>{message}</p></div>`,
            },
         }
      )
   })

You might also use it with Laravel & AlpineJS:

   <div 
      class="flex items-center mt-5 rounded-md consent-message--wrapper"
      x-init="
         $nextTick(() => { 
            window.ConsentMessage.new(
               'functional',
               $el
               )
         })
      "
   >
      <div class="max-w-lg rounded-lg consent-message scale">
         <button class="confirm">
            @svg('play', 'text-primary w-12 h-12 my-3 transition duration-300 hover:scale-125')
         </button>
         <p>Dieses Video wird von <i class="consent-message--source">www.youtube-nocookie.com</i> geladen.<br>Durch das Abspielen werden Daten wie z.B. Ihre IP-Adresse an den externen Server übertragen. Weitere Informationen entnehmen Sie bitte unserer <a href="{{ route('datenschutz')}} " wire:navigate title="Datenschutzerklärung lesen">Datenschutzerklärung</a>.</p>
      </div>
      <iframe 
         width="560"
         class="w-full"
         height="315"
         data-src="https://www.youtube-nocookie.com/embed/_bgy53RBCSk?si=-W9-4ZiJpC-iq0rI"
         title="YouTube video player"
         frameborder="0"
         allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen 
      ></iframe>
   </div>