Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Cookie and privacy consent banner. Non-modal (bottom/top) or modal (center) dialog backed by localStorage.
The <consent-banner> component wraps a native <dialog> to handle cookie and privacy consent. It manages localStorage persistence, expiry, and provides a trigger attribute to let users re-open their preferences from anywhere on the page.
The author controls all content inside the dialog — the component handles the plumbing: show/hide, localStorage read/write, and event dispatch.
The minimal setup: a <dialog> with a paragraph and footer containing accept/reject buttons. The value attribute on each button drives the consent logic.
<consent-banner> <dialog> <p>We use cookies to improve your experience. <a href="/privacy">Privacy Policy</a></p> <footer> <button value="reject" class="secondary">Reject All</button> <button value="accept">Accept All</button> </footer> </dialog></consent-banner>
Add checkboxes for per-category control. The value="save" button stores whatever the user has checked. The value="accept" button marks everything true; value="reject" only keeps disabled checkboxes (typically Necessary
) as true.
<consent-banner persist="cookie-prefs" trigger="#manage-cookies"> <dialog> <header><h2>Cookie Preferences</h2></header> <section> <p>We use cookies to personalize content and analyse traffic. <a href="/privacy">Learn more</a></p> <fieldset class="minimal"> <label> <input type="checkbox" name="necessary" checked disabled> Necessary </label> <label> <input type="checkbox" name="analytics"> Analytics </label> <label> <input type="checkbox" name="marketing"> Marketing </label> </fieldset> </section> <footer> <button value="reject" class="secondary">Reject All</button> <button value="save" class="secondary">Save Preferences</button> <button value="accept">Accept All</button> </footer> </dialog></consent-banner> <!-- Elsewhere on the page --><button id="manage-cookies">Manage Cookies</button>
The position attribute controls where the banner appears and whether it is modal.
| Value | Behavior | Dialog API |
|---|---|---|
bottom (default) |
Fixed to viewport bottom, page remains interactive | dialog.show() |
top |
Fixed to viewport top, page remains interactive | dialog.show() |
center |
Centered modal with backdrop — user must choose (ESC prevented) | dialog.showModal() |
<consent-banner position="top"> <dialog> <p>This site uses cookies. <a href="/privacy">Learn more</a></p> <footer> <button value="reject" class="secondary">Reject</button> <button value="accept">Accept</button> </footer> </dialog></consent-banner>
<consent-banner position="center"> <dialog> <header><h2>Before you continue</h2></header> <section> <p>We need your consent to use cookies. You must make a choice to proceed.</p> </section> <footer> <button value="reject" class="secondary">Reject All</button> <button value="accept">Accept All</button> </footer> </dialog></consent-banner>
Set trigger to a CSS selector pointing at a Manage Cookies
button (or link) elsewhere on the page. When clicked, the dialog re-opens with previously saved checkbox states restored.
The trigger uses document-level event delegation, so the target element can exist anywhere in the DOM — even if it loads after the banner.
The component dispatches a single event when the user makes a consent choice.
| Event | Detail | Description |
|---|---|---|
consent-banner:change |
{ preferences: Object, action: string } |
Fired when the user clicks accept, reject, or save. preferences maps checkbox names to booleans. action is the button’s value. |
The ConsentBanner class exposes static methods for reading and clearing consent from external code.
| Method | Parameters | Returns | Description |
|---|---|---|---|
ConsentBanner.getConsent(key?) |
key: string (default: 'consent-banner') |
{ preferences, action, timestamp } | null |
Read stored consent. Use on page load to check if analytics/marketing scripts should run. |
ConsentBanner.reset(key?) |
key: string (default: 'consent-banner') |
void |
Clear stored consent. The banner reappears on next page load. |
import { ConsentBanner } from '/src/web-components/consent-banner/logic.js'; // Check stored consent on page loadconst consent = ConsentBanner.getConsent('cookie-prefs');if (consent?.preferences?.analytics) { initAnalytics();} // Clear consent (banner reappears on next load)ConsentBanner.reset('cookie-prefs');
| Attribute | Values | Default | Description |
|---|---|---|---|
persist | string | consent-banner | localStorage key for storing consent |
position | "bottom", "top", "center" | bottom | Banner position. Center uses modal dialog. |
trigger | string | — | CSS selector for a re-open button (e.g. #manage-cookies) |
expires | string | 365 | Days until consent expires. 0 or 'never' disables expiry. |
| Element | Required | Description |
|---|---|---|
<dialog> | yes | Banner container — shown until consent is given |
<button value="accept"> | yes | Accept-all action button |
<button value="reject"> | no | Reject-all action button (optional) |
<input type="checkbox"> | no | Granular preference toggles (optional, for save mode) |
<button value="save"> | no | Save granular preferences button (optional, used with checkboxes) |
| Attribute | On | Values | Description |
|---|---|---|---|
value | button | "accept", "reject", "save" | Action button type — accept all, reject all, or save granular preferences |
name | input[type=checkbox] | string | Preference category name stored in the consent cookie |
Consent is stored as JSON under the configured key.
{ "preferences": { "necessary": true, "analytics": true, "marketing": false }, "action": "save", "timestamp": 1709913600000}
The timestamp field enables expiry. When expires days have elapsed since the timestamp, the banner reappears on next page load.
dialog.show() (non-modal). The page remains interactive — users can scroll and navigate freely.dialog.showModal() (modal). Focus is trapped inside the dialog. ESC is prevented to enforce a consent choice.| Key | Action |
|---|---|
| Tab | Move between buttons and checkboxes |
| Enter / Space | Activate button or toggle checkbox |
| Escape | Prevented in center (modal) position; no effect in bottom/top (non-modal) |
When prefers-reduced-motion: reduce is set, the slide-in animation is disabled. The banner appears instantly.
The consent-banner:not(:defined) CSS rule hides the banner until the component is defined. Without JavaScript, the banner never appears and no consent is required — a safe progressive enhancement default.
This is a client-side consent UI helper built around native <dialog>. It stores the user's decision in localStorage. If your project requires a broader compliance workflow (server-side enforcement, cookie-less tracking prevention, or jurisdiction-specific legal requirements), those concerns live outside this component.