KKUONIDesign System

Components

Cookie Banner

Beta

A fixed-position bottom banner for cookie consent. Simple mode shows a single "Accept all" action. Expanded mode lists individual cookie categories with toggles. Persists consent to localStorage.

We use cookies to improve your experience and personalise the content and ads you see. By clicking "Accept all" you consent to our use of cookies. Cookie policy

Purpose

The CookieBanner collects and persists user consent for cookie categories as required by GDPR and the UK PECR. It renders as a fixed bottom-0 overlay so it is always visible on page load until the user interacts.

Two modes:

  • Simple (expanded={false}) — a single message with "Manage preferences" and "Accept all". Use this as the initial state for most users.
  • Expanded (expanded={true}) — a category-by-category breakdown with Toggle switches. Shown when the user clicks "Manage preferences" in simple mode.

Consent is persisted to localStorage under the key kuoni_cookie_consent. The banner does not render on subsequent page loads once the user has consented. localStorage is accessed only inside useEffect to remain SSR-safe.

Note: CookieBanner uses position: fixed and renders as a full-viewport-width overlay. The static mockups on this page represent the component's appearance. In a live Next.js app, mount it once at the layout level (e.g. app/layout.jsx) rather than in individual pages.

Usage

Reach for CookieBanner specifically for cookie consent — it is purpose-built for GDPR/PECR category consent and localStorage persistence. For any other site-wide announcement (a promotion, a service notice, a policy change) use the general-purpose Banner instead; it does not manage consent state.

CookieBanner mounts once at the application layout level and appears at the bottom of the viewport on first visit, ahead of any other page content, until the user makes a consent choice.

Anatomy

Simple mode

We use cookies to improve your experience and personalise the content and ads you see. By clicking "Accept all" you consent to our use of cookies. Cookie policy

  1. 1
    Banner surfaceDark (bg-kuoni-dark) full-width bar at the bottom of the viewport. z-toast.
  2. 2
    Consent messageShort statement explaining cookie use with a link to the Cookie policy page.
  3. 3
    "Manage preferences" buttonGhost button. Opens the expanded category view.
  4. 4
    "Accept all" buttonKuoni Yellow CTA. Saves all categories as accepted and hides the banner.

Expanded mode

Cookie preferences

Manage your cookie settings. Necessary cookies cannot be disabled.

Necessary
Required for the site to function correctly.
Analytics
Help us understand how visitors interact with our site.
Marketing
Used to deliver personalised ads relevant to you.
  1. 1
    Header"Cookie preferences" heading with explanatory sub-text.
  2. 2
    Category rowCategory name, description, and a Toggle switch. Necessary category is always checked and disabled.
  3. 3
    "Reject all" buttonGhost button. Sets analytics and marketing to false and saves.
  4. 4
    "Save preferences" buttonYellow CTA. Saves the current toggle state and hides the banner.

Variants

Simple (initial state)

We use cookies to improve your experience and personalise the content and ads you see. By clicking "Accept all" you consent to our use of cookies. Cookie policy


Expanded (preferences detail)

Shown after clicking "Manage preferences". Toggles for analytics and marketing; necessary is locked on.

Cookie preferences

Manage your cookie settings. Necessary cookies cannot be disabled.

Necessary
Required for the site to function correctly.
Analytics
Help us understand how visitors interact with our site.
Marketing
Used to deliver personalised ads relevant to you.

States

StateBehaviour
First visitBanner visible. Simple mode.
"Manage preferences"Switches to expanded mode.
"Accept all"Saves all categories as true. Banner hides.
"Reject all"Saves optional categories as false. Banner hides.
"Save preferences"Saves current toggle values. Banner hides.
Subsequent visitslocalStorage key present → banner does not render.
Toggle (necessary)Checked and disabled — cannot be unchecked.
Toggle (optional)Checked/unchecked based on user preference.

Best practices

Do

Mount CookieBanner once at the layout level so it is consistent across pages.

Mounting CookieBanner once in app/layout.jsx ensures it appears on every page at the same position, and the localStorage check prevents it from re-appearing after consent.

Don't

Mount CookieBanner inside individual page components.

Adding CookieBanner to individual page components risks showing it on some pages but not others, or displaying it multiple times on pages with multiple instances.

Do

Keep the Necessary cookie category checked and disabled. It is non-negotiable under PECR.

The Necessary category is required for the site to function — locking it prevents users from entering an invalid consent state.

Don't

Add a toggle to the Necessary category that allows users to disable it.

Allowing users to disable necessary cookies makes the site non-functional and creates false expectations about what they can reject.

Do

Gate third-party scripts (analytics, advertising pixels) on the corresponding consent category.

Only firing analytics scripts after the user has accepted analytics cookies ensures GDPR compliance from the first page load.

Don't

Load optional scripts before checking or receiving consent.

Loading Google Analytics on the first page load and then asking for consent makes the initial page load non-compliant.

Props

CookieBanner

PropTypeDefaultDescription
categoriesArray<CookieCategory>Necessary, Analytics, MarketingCookie categories to display in expanded mode. The first category is assumed to be required=true (locked on).
policyHrefstring'#'URL for the Cookie policy link in the simple mode message.
onConsent(consent: Record<string, boolean>) => voidCalled when the user saves preferences or accepts all. Receives a map of category value → boolean.
storageKeystring'kuoni_cookie_consent'localStorage key used to persist consent. Change to namespace multiple sites sharing the same domain.
classNamestringAdditional Tailwind classes on the root banner element.

CookieCategory shape

PropertyTypeDescription
valuestringUnique identifier used as the consent key in localStorage.
labelstringDisplay name shown in the category row.
descriptionstringOne-sentence explanation of what the cookie category does.
requiredbooleanWhen true, the toggle is checked and disabled.
defaultCheckedbooleanInitial checked state for optional categories.

Accessibility

Dialog role

The banner has role="dialog" aria-modal="true" aria-label="Cookie preferences". This signals to assistive technology that the banner is a modal-like region requiring attention:

<div role="dialog" aria-modal="true" aria-label="Cookie preferences">
  …
</div>

Focus management

When the banner becomes visible, focus should move to the first interactive element ("Manage preferences" or the first Toggle in expanded mode). This ensures keyboard users are immediately aware of the banner rather than needing to Tab to it from the top of the page.

Toggle labels

Each Toggle in expanded mode uses the Toggle component's label prop, which renders as sr-only text inside the switch. Screen readers announce the toggle as "Necessary toggle, on, dimmed" or "Analytics toggle, off" — communicating the category name, state, and disabled status.

Keyboard interaction

KeyAction
TabMove focus through buttons / toggles
Enter / SpaceActivate the focused button or toggle
EscapeNo action — banner requires an explicit consent choice
  • Banner — use for general site announcements that are not about cookie consent.
  • Toggle — powers the per-category switches in expanded mode.