Overlays & Feedback

Cookie Consent Banner

A React cookie consent banner with a preferences dialog: necessary/analytics/marketing category toggles, accept-all and reject-all, a consent-object callback and localStorage persistence — SSR-safe with reduced-motion support.

The compliance component every production site needs. A bottom banner slides in after a short delay with three entry points — accept all, reject all, and manage preferences. "Manage preferences" opens a centered dialog with a switch per category (required categories are locked on); saving hands the consent object to onConsent and remembers it in localStorage, so returning visitors with a stored record are never bothered again. The first render outputs nothing at all, and localStorage is only read after mount, so SSR and hydration stay perfectly consistent.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add cookie-consent-banner.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

Active edited usageDefault props
<CookieConsentBanner/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/cookie-consent-banner.en.json

Or, once registries are configured in components.json, install it as @webberui/cookie-consent-banner.

Usage

import {
  CookieConsentBanner,
  getStoredConsent,
} from "@/components/ui/cookie-consent-banner";

<CookieConsentBanner
  privacyHref="/privacy"
  onConsent={(consent, action) => {
    // Load analytics/marketing scripts only when consented
    if (consent.analytics) {
      // initAnalytics();
    }
  }}
/>;

// The stored record can be read back at any time (e.g. for conditional script loading)
const stored = getStoredConsent(); // StoredConsent | null

Props

PropTypeDefaultDescription
categoriesCookieCategory[]3 built-inCookie category list; the default provides necessary (locked), analytics and marketing with Traditional Chinese copy
onConsent(consent: Record<string, boolean>, action: ConsentAction) => void—Callback when the user decides: the consent object (category id → consented) and how the decision was made
storageKeystring"webberui:cookie-consent"localStorage key
persistbooleantrueWhether to read/write localStorage; with false the banner reappears on every mount (for demos or self-managed storage)
position"bottom" | "bottom-left" | "bottom-right""bottom"Where the banner docks: a centered bar or a corner card
privacyHrefstring—Privacy policy URL; when provided a link is appended after the message
messagestring—Main banner copy (a shortcut for labels.message)
showRejectAllbooleantrueWhether to show the reject-all button
labelsCookieConsentLabelsbuilt-in zh copyOverride the UI copy; omitted fields keep the defaults
defaultOpenboolean—Force the banner shown or hidden (for showcases); when omitted, visibility follows whether a localStorage record exists
containerHTMLElement | null—Showcase container: when provided, the banner and dialog mount inside it with absolute positioning (the container needs relative); otherwise they mount fixed on document.body
classNamestring—Forwarded to the banner root element

CookieCategory

FieldTypeDefaultDescription
idstring—Category id, used as the key of the consent object
namestring—Display name
descriptionstring—What this category is used for
requiredbooleanfalseRequired category: its switch is locked on and it is always recorded as consented

CookieConsentLabels

FieldTypeDefaultDescription
titlestring"Cookie Usage Instructions" (About cookies)Banner title
messagestringbuilt-in copyMain banner copy
privacyTextstring"Privacy Policy" (Privacy policy)Privacy link text
acceptAllstring"Accept all" (Accept all)Accept-all button text
rejectAllstring"Reject All" (Reject all)Reject-all button text
managestring"Manage Preferences" (Manage preferences)Manage-preferences button text
savestring"Store Settings" (Save settings)Save button text in the dialog
dialogTitlestring"Cookie Preferences" (Cookie preferences)Dialog title
requiredBadgestring"Required" (Required)Badge text on required categories
closestring"Turn Off Preferences" (Close preferences)Accessible name of the dialog close button

getStoredConsent(storageKey?) and clearStoredConsent(storageKey?) are also named exports, along with the ConsentAction ("accept-all" \| "reject-all" \| "custom"), StoredConsent (the stored shape with consent, action and updatedAt) and CookieConsentPosition types, so the application layer can read, clear and branch on the consent record.

How it works

  • The banner waits about 0.6 s before sliding in — the page's main content gets to enter first, and the consent prompt does not steal the first glance
  • The first render (including SSR) outputs nothing; only after mount does the component read localStorage to decide visibility, avoiding hydration mismatches
  • localStorage is only touched inside effects and event handlers; the stored format is a StoredConsent JSON (consent object, action, ISO timestamp), and when localStorage is unavailable (e.g. private browsing) onConsent still fires so the caller can store the result itself
  • Reject-all and save always record required categories as consented; non-required categories default to off in the dialog, following the "no tracking before consent" principle
  • container is an escape hatch for showcases: docs sandboxes and Storybook can trap the banner inside a fixed-height box; production usage does not need it

Accessibility

  • The banner is a role="region" named by its title, and all three actions are native button elements
  • The preferences dialog is role="dialog" with aria-modal="true": focus moves into it on open, Tab cycles within it, and Esc or a backdrop click closes it and returns focus to the manage-preferences button
  • Category switches use role="switch" with aria-checked; required categories are disabled, locked on, and marked with a badge
  • When the user enables "reduce motion" at the system level, the slide in/out and dialog scale animations become quick cross-fades with no loss of function