Cookie Preferences Panel — Free HTML CSS JS Snippet

Cookie Preferences Panel · Modals · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Per-category consent
A labelled toggle for each cookie category, the granular control GDPR and ePrivacy require — not a single accept button.
Native checkbox switches
Each toggle is a real <input type=checkbox> styled via :checked/:disabled siblings, so it is keyboard- and screen-reader-friendly.
Locked necessary category
Strictly-necessary is checked, disabled, badged "Always on", and skipped by bulk actions so it can never be turned off.
Opt-in defaults
Optional categories start off, matching the GDPR rule that non-essential cookies require active consent.
Single state object
One state object is the source of truth, updated through a delegated change listener on the whole list.
Accept / Reject / Save
Bulk accept and reject flip all optional toggles; save persists the current selection, all via one save() hook.
Proper dialog semantics
role=dialog, aria-modal, aria-labelledby, and a focus-visible ring make the panel accessible.
Responsive actions
The button row wraps to full-width stacked buttons under 420px for mobile.

About this UI Snippet

Cookie Preferences Panel — Per-Category Consent Toggles with Accept, Reject and Save

Screenshot of the Cookie Preferences Panel snippet rendered live

Privacy laws like GDPR and ePrivacy require granular cookie consent: users must be able to accept or reject each category of non-essential cookies, and "strictly necessary" cookies must be clearly marked as always-on. A single "Accept" button is not compliant. This component is a proper cookie preferences panel — a modal with a labelled toggle for each cookie category, a locked necessary category, and three actions (Accept all, Reject all, Save choices) — built in HTML, CSS, and vanilla JavaScript, ready to wire to your real consent storage and script loading.

Per-category toggles built on real checkboxes

The panel is generated from a CATEGORIES array of { key, title, desc, required, on } objects. Each renders a row with the category name, a description of what it does, and a switch. The switch is a styled native <input type="checkbox"> — not a div pretending to be a toggle — so it is keyboard-operable (Space toggles it), focusable, and announced correctly by screen readers. The visual switch (track and sliding knob) is drawn by CSS on a sibling element using the :checked and :disabled sibling selectors, so the appearance always reflects the real input state. A :focus-visible ring makes keyboard focus obvious.

The locked "necessary" category

The strictly-necessary category is required: true, which renders its checkbox checked and disabled and adds an "Always on" badge. Disabled means the user cannot turn it off (necessary cookies are exempt from consent), and the bulk Accept/Reject actions explicitly skip required categories so they are never changed. This is the compliance-critical detail: essential cookies stay on, everything else defaults to off until the user opts in.

Consent defaults to off

Note that analytics, marketing, and functional all start on: false. Under GDPR, non-essential cookies must be off by default — consent has to be an active opt-in, not a pre-ticked box the user must notice and uncheck. The panel reflects that: optional toggles are off until the user enables them or clicks Accept all.

Three actions, one state object

A state object mirrors every category's on/off value and is the single source of truth. Toggling a switch updates state via a delegated change listener on the list (one listener for all rows, rather than one per switch). "Accept all" and "Reject all" call setAll(), which flips every non-required category in both the state object and the visible checkboxes. "Save choices" persists whatever the current toggles are. All three then call save(), which here flashes a confirmation showing the resulting consent object — the exact shape you would store.

Wiring to real consent

The save() function is where you connect reality: persist the state object to a cookie or localStorage, then conditionally load the scripts the user consented to (fire your analytics snippet only if state.analytics is true, your ad pixels only if state.marketing is true, and so on). On a return visit you would read the stored consent, set the toggles to match, and skip the panel if a choice was already made. The component handles all the UI; you provide the storage and the script gating.

The modal presentation

The panel sits in a fixed overlay with a blurred, dimmed backdrop, centred, with role="dialog", aria-modal="true", and aria-labelledby pointing at the title — the correct dialog semantics. The action buttons wrap to full width on narrow screens (under 420px) so the three-button row never crowds on mobile. Swap the #6366f1 accent for your brand and edit the categories and copy to match your cookie policy.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the CATEGORIES-to-DOM pipeline by hand to see how one array drives the whole panel. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the change listener is attached once to the list container rather than once per checkbox, and why setAll explicitly skips any category marked required instead of relying on the disabled attribute alone to protect it. The same assistant can help optimize it — for instance asking whether generating the row markup with string concatenation and innerHTML is safe if category titles or descriptions ever come from user-editable data versus a hardcoded array. It's also useful for extending the panel: ask it to persist the state object with a policy-version number so consent is invalidated when the cookie policy changes, add a "why we ask" expandable detail per category, or wire save() to conditionally inject real analytics and ad scripts based on the final state values. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a granular cookie-preferences panel in plain HTML, CSS, and JavaScript driven entirely by a single data array — no consent-management library, no frameworks.

Requirements:
- Define a single array of cookie category objects (key, title, description, whether required, and default on/off state), and generate every row of the panel's UI dynamically from that array by iterating it once — no hand-written per-category markup.
- Each category row must include a real checkbox input styled as a toggle switch via a sibling element and CSS selectors targeting the checkbox's checked and disabled states, so the switches are keyboard-operable and screen-reader accessible, not just divs with click handlers.
- The category marked required must render checked and disabled with a visible "always on" badge, and must be structurally impossible for any bulk action to change: the accept-all and reject-all functions must explicitly skip any category flagged required when iterating, rather than relying only on the disabled attribute to prevent the change.
- All non-required categories must default to off, reflecting an opt-in rather than opt-out consent model.
- Maintain one plain state object that mirrors every category's current on/off value as the single source of truth, updated through one delegated change event listener attached to the whole list container rather than one listener per individual checkbox.
- Three actions — Accept All, Reject All, Save Choices — where the first two both set every non-required category's state and checkbox to true or false respectively and the third simply reads current values, and all three must call one shared save function that shows a confirmation message built dynamically from the actual current state object (not a hardcoded string).
- Present the whole thing as a modal dialog with proper role, aria-modal, and aria-labelledby attributes, and make sure the confirmation message uses an aria-live region so screen reader users are notified of the outcome.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSA centered cookie preferences modal renders with four categories, each with a description and a toggle switch.
  2. 2
    Toggle a categoryFlip analytics, marketing, or functional on or off; the strictly-necessary category is locked on with an "Always on" badge.
  3. 3
    Use the bulk actionsAccept all turns every optional category on, Reject all turns them off, and Save choices keeps the current toggles — necessary is never changed.
  4. 4
    See the resulting consentA confirmation flashes showing the consent object you would store.
  5. 5
    Edit the categoriesChange the CATEGORIES array — titles, descriptions, defaults, and which are required — and the rows rebuild.
  6. 6
    Wire to storage and scriptsIn save(), persist the state to a cookie/localStorage and conditionally load only the scripts the user consented to.

Real-world uses

Common Use Cases

GDPR / ePrivacy compliance
Give EU/UK visitors granular cookie control — pair the first-visit prompt with a slim cookie banner that opens this panel via "Manage preferences".
Privacy settings pages
Reuse the same toggles in account settings so users can change consent any time; compare with a GDPR consent manager.
Marketing sites with analytics
Gate analytics and ad pixels behind real consent before they load.
SaaS and dashboards
Offer functional-cookie control for preferences like language and region.
Agencies building client sites
A drop-in, themeable consent panel that meets the granular-choice requirement; complements cookie preferences.
Learning consent UX
A reference for accessible toggle switches, opt-in defaults, and the locked-necessary pattern.

Got questions?

Frequently Asked Questions

The panel manages choices; you enforce them in save(). Persist the state object to a cookie or localStorage, then load category scripts conditionally: only inject your analytics tag if state.analytics is true, your ad pixels if state.marketing is true, and so on. The key is that those scripts must not run before consent — load them dynamically inside save() (or on a return visit after reading stored consent), not statically in the page head.

GDPR requires that consent for non-essential cookies be an active opt-in — pre-ticked boxes are explicitly not valid consent. So analytics, marketing, and functional start off, and only the strictly-necessary category (which is exempt from consent) is on and locked. The user must deliberately enable or Accept all to turn optional categories on.

A native <input type=checkbox> is keyboard-operable (Space toggles), focusable, and announced by screen readers as a checkbox with its state — all for free. The visual switch is just CSS drawn on a sibling element via the :checked and :disabled selectors, so the look always matches the real input. A div toggle would require re-implementing all that accessibility manually and is easy to get wrong.

In save(), write the state object (and a timestamp/version) to a cookie or localStorage. On page load, read it: if a valid consent record exists, set each toggle to the stored value and skip showing the panel (or just load the consented scripts). Re-prompt if your cookie policy changes by bumping a version number that invalidates old consent. Always provide a way to reopen the panel so users can change their mind.

Hold the consent object in state and render CATEGORIES with .map/v-for/*ngFor, binding each checkbox's checked/disabled to the category. The change handler updates the state for that key; Accept/Reject set all non-required keys. Move save() into a function that persists to storage and triggers your script-loading logic. The CSS — the switch, the modal, the layout — ports unchanged; consider a context/store so other components can read consent before loading scripts.