Cookie consent
A consent bar that morphs into preferences and folds into a reopen button.
pnpm dlx shadcn@latest add @uiarc/cookie-consent- Asking for cookie or tracking consent on a marketing site or app.
- Sites that need per-category choices with a way to reopen them later.
- Use announcement-bar for a site-wide promotion or notice that needs no choice.
- Use toast for a one-off message that dismisses itself.
- Use dialog when the page must block until the visitor decides.
Installation
Add Cookie consent with the shadcn CLI, or copy the source by hand.
pnpm dlx shadcn@latest add @uiarc/cookie-consentAdds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:
import { useRef } from "react";import { CookieConsent, type CookieConsentHandle } from "@/components/arc/cookie-consent/cookie-consent"; export function Consent() { const consent = useRef<CookieConsentHandle>(null); return ( <> <CookieConsent ref={consent} storageKey="acme-consent" policy={{ label: "Cookie policy", href: "/cookies" }} onChange={value => { if (value?.analytics) loadAnalytics(); }} /> <button type="button" onClick={() => consent.current?.openPreferences()}>Cookie settings</button> </> );}API reference
3 parts. The first is the root.
CookieConsent
A consent bar that morphs into a preferences panel and then folds into a small reopen button, with optional localStorage memory.
categoriesCookieCategory[]defaultCookieCategoriesGroups: { id, label, description?, required?, defaultEnabled? }. Required ones are always on.valueCookieConsentValue | null–Controlled saved choice (category id to allowed). null means the visitor has not decided.defaultValueCookieConsentValue | nullnullStarting choice when uncontrolled.onChange(value: CookieConsentValue | null, detail: { action: CookieConsentAction; updatedAt: Date }) => void–Called on accept-all, reject-all, save, or reset. Load optional scripts from here.storageKeystring–Remembers the choice in localStorage under this key. Read it elsewhere with readCookieConsent.versionstring"1"Bump when categories or policy change; a stored choice for another version is ignored and the bar asks again.preferencesOpenboolean–Controlled open state of the preferences panel.defaultPreferencesOpenbooleanfalseOpens the panel on first render when uncontrolled.onPreferencesOpenChange(open: boolean) => void–Called when the panel opens or closes.policy{ label: string; href?: string; onClick?: () => void }–Link to the full policy, shown in the bar and panel. Renders a button when href is missing.position"bottom" | "bottom-left" | "bottom-right""bottom"Where the bar sits along the bottom edge.strategy"fixed" | "absolute""fixed"fixed pins to the viewport; absolute fills the nearest positioned ancestor, for previews.showReopenButtonbooleantrueKeeps a 44px cookie button after a choice so visitors can change their mind.reopenSide"left" | "right"–Corner for the reopen button. Defaults to the bar's side, or left when centered.labelsPartial<CookieConsentLabels>–Overrides for message, customize, acceptAll, rejectAll, save, preferencesTitle, preferencesDescription, reopen, alwaysOn, and close.classNamestring–Class on the layer.refRef<CookieConsentHandle>–Imperative handle: openPreferences(), closePreferences(), reset().readCookieConsent
Reads a saved choice as { value, version, updatedAt }, or null when there is none or it was saved for another version. Safe on the server.
storageKeyRequiredstring–The same key passed to CookieConsent.versionstring"1"The version the choice must match.defaultCookieCategories
The default categories: Strictly necessary (required), Preferences, Analytics, and Marketing.
No props.
- Tab
- Moves through the bar or panel buttons and switches.
- Space
- Toggles the focused category switch.
- Escape
- Closes the preferences panel without saving and returns focus to the bar or reopen button.
- The bar and panel are non-modal dialogs; the bar is described by its message and the panel is labelled by its heading.
- Opening the panel focuses its heading; saving focuses the reopen button, which has aria-haspopup="dialog" and the reopen label.
- Each switch is labelled by its category and described by its hint; required categories show Always on text instead of a switch.
- Save is described by a hidden count such as 2 of 3 optional categories allowed.
- Reject all sits beside Accept all at the same weight, so refusing is as easy as accepting.
- One surface springs its position, width, height, and radius between the bar, the panel, and the 44px reopen button; faces rise from a short blur.
- Growing uses a slightly bouncy spring and shrinking a flat one, so the surface never overshoots when it folds away.
- The surface enters rising 24px and scaling from 0.98.
- Reduced motion jumps the surface to each shape and swaps faces with a plain fade.
- The bar is at most 660px and the panel 420px wide, both clamped to the layer, which sits 16px from the viewport edges.
- Below a 560px bar width the message stacks above the actions; panel actions go on one row at a 340px container width.
- The layer ignores pointer input, so only the surface blocks clicks on the page behind it.
- Nothing renders until hydration and the stored choice are read, so a decided visitor never sees the bar flash.
- One ResizeObserver tracks the layer width and one tracks the current face; motion runs only when the shape changes.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Mount once near the root layout. Gate optional scripts on onChange or on readCookieConsent(storageKey, version) at startup.
- Bump version whenever categories or the policy change to ask everyone again.
- Wire a footer Cookie settings link to ref.openPreferences(). Set showReopenButton={false} if that link is the only way back.
- Use strategy="absolute" inside a positioned preview frame, not for production.
The full library index for assistants is at /llms.txt.