Cookie consent for GDPR / ePrivacy and Turkey's KVKK, wired to Google Consent Mode v2. Not a modal: a labelled region that never blocks the page or steals focus, with “Reject all” and “Accept all” at equal size and weight on the first layer. Preferences open in a Dialog with one Switch per category, none pre-checked. Choices live in a first-party vb_consent cookie (6 months, versioned); getConsent / onConsent let other code wait for a category, and ConsentGate keeps maps and videos behind a facade. Without JS nothing renders and no non-essential cookie is set. Default texts are neutral templates — review them with counsel.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { ConsentBanner, consentDefaultsScript, onConsent } from "@vamos-ui/react/blocks";
// app/layout.tsx
<head><script dangerouslySetInnerHTML={{ __html: consentDefaultsScript({ version: 2 }) }} /></head>
<body>
{children}
<ConsentBanner version={2} policyLink={{ href: "/cookies" }} />
</body>
// anywhere on the client
onConsent("analytics", () => loadAnalytics());Examples
Box (Turkish)
A 360px panel for the bottom-start corner, buttons stacked; zorunlu / işlevsel / analitik / pazarlama.
Preferences
Open “Preferences”: Switches with descriptions, Necessary locked on, Reject / Accept / Save.
Google Consent Mode v2
The head snippet sets everything to denied, then replays a stored choice before any Google tag loads. Each choice calls gtag('consent','update').
window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('consent','default',{"ad_storage":"denied","ad_user_data":"denied","ad_personalization":"denied","analytics_storage":"denied","functionality_storage":"denied","personalization_storage":"denied","security_storage":"granted","wait_for_update":500});gtag('set','ads_data_redaction',true);try{var m=document.cookie.match(/(?:^|; )vb_consent=([^;]*)/);if(m){var r=JSON.parse(decodeURIComponent(m[1]));if(r&&r.v>=2&&r.c){var g=function(k){return r.c[k]?'granted':'denied'};gtag('consent','update',{ad_storage:g('marketing'),ad_user_data:g('marketing'),ad_personalization:g('marketing'),analytics_storage:g('analytics'),functionality_storage:g('functional'),personalization_storage:g('functional')});}}}catch(e){}ConsentGate for maps and video
A static preview with one button; the iframe loads on click, or automatically once the category is granted. “Always load maps” grants functional for the site.
The map loads from Google Maps, which may set cookies.
Hosted by YouTube, which may set cookies.
API
ConsentBanner
| Prop | Type | Default |
|---|---|---|
variantBottom strip, or a corner panel. | "bar" | "box" | "bar" |
textThe notice; rich text may contain links. | ReactNode | RichDoc | neutral template |
policyLinkCookie policy link. | { href; label? } | — |
categoriesNecessary is always on. | ("necessary" | "functional" | "analytics" | "marketing")[] | all four |
versionBump when text or categories change: visitors are asked again. | number | 1 |
positionDefault bottom (bar) / bottom-start (box). | "bottom" | "bottom-start" | — |
regionModeopt-in for GDPR/KVKK; notice only where the law allows. | "opt-in" | "notice" | "opt-in" |
contained / forceShow / defaultPreferencesOpenPreviews and builder canvases. | boolean | — |
labels / localeAll banner and dialog strings (site2LabelsTr). | Partial<Site2Labels> / string | — |
Consent helpers
| Prop | Type | Default |
|---|---|---|
getConsent(category, version?)Is the category granted now? | boolean | — |
onConsent(category, fn)Runs fn once the category is granted (now or later). Returns unsubscribe. | () => void | — |
writeConsent / readConsent / parseConsent(cookieHeader)The vb_consent record; parseConsent also works on the server. | functions | — |
openConsentPreferences()Reopens the dialog (Footer's “Cookie settings” uses ConsentSettingsButton). | void | — |
consentDefaultsScript(opts) / <ConsentDefaults />Consent Mode v2 defaults for <head>. | string / element | — |
ConsentGate
| Prop | Type | Default |
|---|---|---|
src / titleIframe URL and its accessible title. | string | — |
categoryGranted category that loads it automatically. | ConsentCategory | "functional" |
loadLabel / notice / rememberLabelButton, provider notice, optional “always load” checkbox. | string / ReactNode / string | — |
aspect / allow / defaultLoadedFrame ratio, iframe allow list, load at once. | string / string / boolean | — |
childrenStatic preview (decorative). | ReactNode | — |