@naturalcycles/cookie-monster
v2.20.0
Published
NC in-house cookie banner that doesn't suck.
Readme
CookieMonster
NC in-house cookie banner that doesn't suck.
Installation
pnpm add @naturalcycles/cookie-monsterScript tag (CDN)
For 3rd-party sites that don't bundle this package as an npm dependency, CookieMonster is also served as a standalone browser bundle via jsDelivr (backed by the same npm publish, so versions always match):
<head>
<script
async
src="https://cdn.jsdelivr.net/npm/@naturalcycles/[email protected]/dist/cookiemonster.js"
></script>
<script>
document.addEventListener('CookieMonsterReady', () => {
globalThis.cookieMonsterService.register({ language: 'en-US' })
})
</script>
</head>See the jsDelivr package page for version pinning options.
Usage
Basic Setup
- Register the custom element:
import { cookieMonsterService } from '@naturalcycles/cookie-monster'
cookieMonsterService.register()- Add the element to your HTML:
<cookie-monster display></cookie-monster>Skip step 2 entirely if you'd rather let CookieMonster manage the element itself — register()
already does, see below.
register() owns the full "should the banner show" decision, before the banner ever renders:
- Reads the existing
CookieConsentcookie, if any. - If there's no existing consent and the browser signals Global Privacy Control, automatically opts the user out of everything but necessary cookies and persists that as the consent — the banner is simply never shown to them.
- Otherwise, if there's still no consent on file, it opens the banner itself — creating and
appending the
<cookie-monster>element if one isn't already in the DOM. NogetConsent()/open()check is needed on your side for the initial "first-time visitor" case. - Wires up the consent audit log (see below), which every future consent decision appends to.
cookieMonsterService.register({
domain: '.example.com', // optional Cookie `Domain` attribute, e.g. to share consent across
// subdomains. Unset by default (host-only cookie).
language: 'en-US',
})Call cookieMonsterService.open()/openSettings() yourself only when you want to re-open it
later — e.g. a "Manage cookie settings" link on a Cookie Policy page (see "Opening and closing
programmatically" below).
Reading consent
import { cookieMonsterService } from '@naturalcycles/cookie-monster'
cookieMonsterService.getConsent() // CookieMonsterConsent | undefined
cookieMonsterService.getConsentLog() // ConsentLogEntry[] | undefined - compliance/audit trailgetConsent() reads the CookieConsent cookie directly, so it reflects the latest decision whether
it came from the banner, settings, or GPC auto opt-out. getConsentLog() returns the append-only
history of consent decisions (capped at the most recent 20), stored in localStorage separately
from the cookie — each entry records the consent, a timestamp, which CookieMonster version the user
saw, and a snapshot of the rendered banner copy. Like getConsent(), it returns undefined rather
than an empty array when there's no log yet (or under SSR).
Opening and closing programmatically
import { CookieMonsterView, cookieMonsterService } from '@naturalcycles/cookie-monster'
cookieMonsterService.open() // shows the banner (or whichever view was last shown)
cookieMonsterService.openSettings() // shows the settings view directly
cookieMonsterService.open({ view: CookieMonsterView.banner }) // explicitly show the banner view
cookieMonsterService.close() // hides itopenSettings() (shorthand for open({ view: CookieMonsterView.settings })) is the call a Cookie
Policy page's "Manage cookie settings" link should use, once register() has run somewhere on that
page (typically once, at bootstrap — register() defines the custom elements open() needs, in
addition to its own consent/auto-open logic). It works whether or not register() already
auto-opened the banner for this visitor: it creates the <cookie-monster> element if none is
mounted yet, or just switches an already-open one to the settings view.
Attributes
| Attribute | Type | Default | Description |
| ----------------------- | --------- | ------- | ------------------------------------------ |
| display | boolean | false | Controls visibility of the cookie banner |
| language (deprecated) | string | en-US | UI language/locale (falls back to en-US) |
Language
- Default:
en-US - Supported locales:
en-US,en-GB,sv-SE,de-DE,fr-FR,es-US,it-IT,pt-BR,fi-FI,da-DK,no-NO - Any unsupported/invalid value falls back to
en-US
Set it via register({ language }) / open({ language }) (see Usage).
Deprecated: setting a
languageattribute directly on<cookie-monster>still works, but we're moving away from configuring the element via attributes — prefer the JS API above.
Events
All events bubble and are composed (accessible outside Shadow DOM).
import { CookieMonsterEvent } from '@naturalcycles/cookie-monster'
const cm = document.querySelector('cookie-monster')
cm?.addEventListener(CookieMonsterEvent.cookieMonsterOnClose, (e: Event) => {
const { consent, reason } = (e as CustomEvent<CookieMonsterOnCloseDetails>).detail
console.log({ consent, reason })
})Available Events
| Event | Trigger | Detail |
| ------------------------------- | ---------------------------- | ------------------------------ |
| cookieMonsterOnMount | Element mounted to DOM | - |
| cookieMonsterOnDisplay | display attribute changed | CookieMonsterOnDisplayDetail |
| cookieMonsterOnClose | Final consent decision made | CookieMonsterOnCloseDetails |
| cookieMonsterOnSettingsViewed | Settings view opened | CookieMonsterReasonDetail |
| cookieMonsterOnBackToBanner | Back from settings to banner | CookieMonsterReasonDetail |
Event Details
interface CookieMonsterReasonDetail {
reason: CookieMonsterEventReason
}
interface CookieMonsterOnCloseDetails {
reason: CookieMonsterEventReason
consent: CookieMonsterConsent
}
interface CookieMonsterOnDisplayDetail {
display: boolean
}
enum ConsentSource {
explicit = 'explicit', // the visitor made a choice in the banner/settings UI
gpc = 'gpc', // auto opt-out, derived from the browser's Global Privacy Control signal
}
interface CookieMonsterConsent {
necessary: boolean // Always true
preferences: boolean
statistics: boolean
marketing: boolean
source: ConsentSource
}
enum CookieMonsterDropdownDetail {
DropdownOpen = 'DropdownOpen', // internal, used between settings dropdowns — not dispatched on <cookie-monster>
}
interface ConsentLogEntry {
date: string
consent: CookieMonsterConsent
version?: string // CookieMonster's own package version
text?: string // snapshot of the rendered banner copy
}Event Reasons
AcceptAll/SaveSettings- User consent actionsBackButton- Back button in settingsOpenSettings- Settings button clicked
TypeScript Support
The package includes full TypeScript definitions. Import types as needed:
import type {
CookieMonsterEvent,
CookieMonsterEventReason,
CookieMonsterConsent,
ConsentSource,
CookieMonsterOnCloseDetails,
CookieMonsterOnDisplayDetail,
CookieMonsterReasonDetail,
CookieMonsterDropdownDetail,
CookieMonsterView,
OpenCookieMonsterOptions,
RegisterCookieMonsterOptions,
ConsentLogEntry,
} from '@naturalcycles/cookie-monster'Development
# Run tests
pnpm test
# Run E2E tests
pnpm e2e