Clinia UI

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 same cc_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-consent

We use cookies to understand how our website is used and to support our marketing. Privacy policy.

Usage

tsx
// 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 CookieConsent once, 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 in onChange.
  • Decline sits beside Accept, same size and weight. Never hide it behind Customize.
  • Global Privacy Control turns marketing off 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 revision when 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

banner

We use cookies to understand how our website is used and to support our marketing. Privacy policy.

preferences

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.

preferences · marketing locked by Global Privacy Control

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 message and each category's description in 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

  1. Load the page with no stored choice: the banner appears. Tab reaches Customize, Decline and Accept, and Privacy policy.
  2. Press Customize: focus moves to Cookie preferences. Each switch announces its category and purpose.
  3. Turn on Global Privacy Control in the browser and reload: Marketing is off, locked, and says why.
  4. Decline, then reload: the banner stays hidden and no marketing or analytics requests leave the page.
  5. In Playwright or another automated browser, the banner shows as it does for users, so axe and screenshots cover it. @clinia/web-cookiebot hides it from automated browsers unless Chromium starts with --disable-blink-features=AutomationControlled.

API

CookieConsent

PropValuesDefaultDescription
categories
CookieCategory[]
–

The categories, in the order they're listed. Mark strictly necessary ones required.

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. false ignores the signal.

onChange
((accepted: string[]) => void)
–

Called with the accepted category ids on first consent and on every change.

CookieConsentCard

PropValuesDefaultDescription
categories
CookieCategory[]
–

The categories, in the order they're listed. Mark strictly necessary ones required.

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 marketing under Global Privacy Control.

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.