Cookie Consent
Asks each user once which cookies they accept, and lets them change it later.
When to use
- Any Clinia app or site that sets cookies beyond the strictly necessary ones: analytics, marketing, session replay.
- A site on
@clinia/web-cookiebot: Cookie Consent replaces the package. It reads the samecc_cookie, so users keep the choice they made.
When not to use
- A site with only necessary cookies: no banner is needed. Say so on the privacy policy.
- Asking for anything other than cookie consent, such as a newsletter or terms: use Dialog or Alert.
Install
npx shadcn@latest add @clinia/cookie-consentWe use cookies to understand how our website is used and to support our marketing. Privacy policy.
Usage
// app/layout.tsx (client component)
import { CookieConsent, openCookiePreferences } from "@/components/ui/cookie-consent";
<CookieConsent
privacyHref="/privacy"
categories={[
{ id: "necessary", title: "Necessary", description: "Keeps the site working.", required: true },
{
id: "analytics",
title: "Analytics",
description: "Google Analytics helps us understand how the site is used.",
cookies: [{ name: "_ga*", vendor: "Google", expiration: "2 years", purpose: "Counts visits." }],
},
{ id: "marketing", title: "Marketing", description: "LinkedIn Insight Tag measures our campaigns." },
]}
onChange={(accepted) => console.log(accepted)}
/>
// Footer
<button onClick={openCookiePreferences}>Manage cookies</button>Clinia rules
- Mount
CookieConsentonce, in the root layout. It stays hidden once the user has chosen. - Nothing optional runs before a choice: every category starts off. Gate each tag with
<script type="text/plain" data-category="analytics">, or read the accepted list inonChange. - Decline sits beside Accept, same size and weight. Never hide it behind Customize.
- Global Privacy Control turns
marketingoff and locks its switch, even over an earlier Accept. Keep it on: under CCPA, a browser's opt-out signal must be honoured. - List every cookie in its category's
cookies: name, vendor, expiry and purpose. Users see it under See cookies, and turning a category off clears them. - Bump
revisionwhen a category or the policy changes: everyone is asked again. - Add a Manage cookies link in the footer that calls
openCookiePreferences(). - Pass the site's copy through
labels, one set per language. The component carries no site-specific text.
States
We use cookies to understand how our website is used and to support our marketing. Privacy policy.
Cookie preferences
- NecessaryAlways on
Keeps the site working: sign-in and security.
- Analytics
Google Analytics helps us understand how the site is used.
- Marketing
LinkedIn Insight Tag measures our campaigns.
Cookie preferences
- NecessaryAlways on
Keeps the site working: sign-in and security.
- Analytics
Google Analytics helps us understand how the site is used.
- Marketing
LinkedIn Insight Tag measures our campaigns. Turned off by your browser's Global Privacy Control setting.
Toggle the site theme to review both themes.
Accessibility
Built in
- The banner is a labelled region, not a modal: it never traps focus or blocks the page, and Tab reaches it like any other content.
- Customize and Back move focus into the new view, so a keyboard or screen-reader user never lands on nothing.
- Each category is a Switch named by its title and described by its purpose. Necessary and blocked categories are disabled, and the reason is part of the description.
- No motion: the card appears and swaps views without animation.
Your responsibility
- Write
messageand each category'sdescriptionin plain language: what it does for the user, not the vendor's name alone. - Put the Manage cookies link where users expect it, usually the footer, and keep it reachable by keyboard.
How to test
- Load the page with no stored choice: the banner appears. Tab reaches Customize, Decline and Accept, and Privacy policy.
- Press Customize: focus moves to Cookie preferences. Each switch announces its category and purpose.
- Turn on Global Privacy Control in the browser and reload: Marketing is off, locked, and says why.
- Decline, then reload: the banner stays hidden and no marketing or analytics requests leave the page.
- In Playwright or another automated browser, the banner shows as it does for users, so axe and screenshots cover it.
@clinia/web-cookiebothides it from automated browsers unless Chromium starts with--disable-blink-features=AutomationControlled.
API
CookieConsent
| Prop | Values | Default | Description |
|---|---|---|---|
| categories | CookieCategory[] | – | The categories, in the order they're listed. Mark strictly necessary ones |
| labels | Partial<CookieConsentLabels> | – | Override any label: pass the site's copy per language. |
| privacyHref | string | – | Link shown after the message. |
| revision | number | 0 | Bump when the categories or the policy change: every user is asked again. |
| gpc | false | { block: string[]; } | { block: ["marketing"] } | Categories Global Privacy Control forces off. |
| onChange | ((accepted: string[]) => void) | – | Called with the accepted category ids on first consent and on every change. |
CookieConsentCard
| Prop | Values | Default | Description |
|---|---|---|---|
| categories | CookieCategory[] | – | The categories, in the order they're listed. Mark strictly necessary ones |
| labels | Partial<CookieConsentLabels> | – | Override any label: pass the site's copy per language. |
| privacyHref | string | – | Link shown after the message. |
| defaultView | bannerpreferences | banner | The view it opens on: the banner, or the preferences a "Manage cookies" link opens. |
| defaultSelected | string[] | [] | Categories switched on when the preferences open. Required categories are always on. |
| locked | string[] | [] | Categories forced off and locked, such as |
| onAccept | (() => void) | – | Called when the user accepts every category. |
| onDecline | (() => void) | – | Called when the user declines; only required categories stay on. |
| onSave | ((accepted: string[]) => void) | – | Save in the preferences, with the categories switched on. |
Related