> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Cookie consent

> GDPR / CCPA / global compliance banner powered by vanilla-cookieconsent. Opt-in by default, gates analytics scripts, ships with en + zh i18n out of the box.

Cookie consent is the work every public website needs and almost every indie
postpones until a buyer's legal team flags it. vibestrap ships a working
banner so you don't have to choose between "shipping fast" and "compliant
on day one":

* 🍪 **Opt-in by default** — analytics scripts (Google Analytics, Microsoft
  Clarity, PostHog) wait for the user's "Accept" before loading. No data
  collected before consent.
* ⚖️ **Globally-framed compliance** — covers GDPR (EU/UK), CCPA / CPRA
  (California), LGPD (Brazil), PIPL (China) under one "Your Privacy
  Choices" mechanism; opt-out is one click from the banner or footer.
* 🧹 **Cleanup on revoke** — when a user toggles analytics off after
  accepting, we erase the cookies that were set. GDPR Article 7(3).
* 🌍 **Bilingual** — en / zh follow vibestrap's existing i18n, no extra
  language detection.
* 🎨 **Two-button banner** — Accept / Reject only, no third "Customize"
  button. Granular per-category control lives behind the footer "Cookie
  Preferences" link (Hick's law: fewer choices = faster decision).
* 🛠️ **Built on
  [`vanilla-cookieconsent`](https://github.com/orestbida/cookieconsent)**
  (9k+ stars, MIT). Easy to retheme via CSS variables.

Architecture is intentionally split into three layers:

| Layer    | File                                               | What it does                                  |
| -------- | -------------------------------------------------- | --------------------------------------------- |
| Library  | `vanilla-cookieconsent`                            | DOM injection, GDPR / CCPA logic, persistence |
| Wrapper  | `src/components/cookie-consent/cookie-consent.tsx` | React Provider, language hookup, lifecycle    |
| Consumer | `useConsent()` in `src/analytics/analytics.tsx`    | Gates the actual tracking scripts             |

## Prerequisites

* Nothing to install — `vanilla-cookieconsent` is already a dependency.
* `siteConfig.cookieConsent.enable` defaults to `true` (the banner ships on).

## Step-by-step

### 1. Confirm it's enabled in `src/config/site.ts`

```ts theme={null}
cookieConsent: {
  enable: true,        // mounts the banner
  revision: 1,         // bump this when cookies change to force re-consent
  expiresAfterDays: 365,
},
```

### 2. Verify the analytics gating

In `src/analytics/analytics.tsx` you'll see scripts split into two buckets:

```ts theme={null}
{/* No consent needed — cookie-free providers */}
{siteConfig.analytics.vercel && <VercelAnalytics />}
{siteConfig.analytics.plausible && <PlausibleScript />}
{siteConfig.analytics.umami && <UmamiScript />}

{/* Consent-gated — drops cookies */}
{siteConfig.analytics.googleAnalytics && allowAnalytics && <GoogleAnalytics />}
{siteConfig.analytics.posthog && allowAnalytics && <PostHogScript />}
{siteConfig.analytics.clarity && allowAnalytics && <ClarityScript />}
```

`allowAnalytics` is `true` only after the user clicks Accept (or already
consented in a previous visit, within the 12-month expiry window).

### 3. Bump revision when cookies change

If you add a new analytics provider or switch one out, bump
`siteConfig.cookieConsent.revision` by one. Every previously-consented
user will see the banner again so they can re-consent under the new
policy. This is GDPR best practice.

### 4. Verify in the browser

```bash theme={null}
pnpm dev
```

In dev mode, the banner is intentionally skipped so you don't see it on
every reload (see `process.env.NODE_ENV` check in
`cookie-consent.tsx`). To see the live banner:

```bash theme={null}
pnpm build && pnpm start
# or visit production
```

You should see:

* A bottom-right toast on first visit
* Two buttons: **Accept all** / **Reject all**
* A footer with: Privacy Policy · Cookie Policy · Your Privacy Choices

## User journey — every scenario, aligned to industry leaders

The following matrix maps every common user flow to vibestrap's behavior
and the corresponding behavior at Stripe / Microsoft / Vercel /
Cloudflare / Linear / OpenAI. The implementation matches the **industry
norm everywhere** — no proprietary or surprising behavior.

| Scenario                                                        | vibestrap behavior                                           | Industry standard                                   |
| --------------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------- |
| **First-ever visit**                                            | Banner appears bottom-right                                  | Same (Stripe / Vercel / Linear / OpenAI)            |
| **Visit after Accept (within 13 months)**                       | No banner, GA + Clarity load                                 | Same                                                |
| **Visit after Reject (within 13 months)**                       | No banner, GA + Clarity stay disabled                        | Same                                                |
| **Visit after consent expires (>13 months)**                    | Banner reappears (treated as new visit)                      | Same — Stripe/MS use 13mo, others 6-12mo            |
| **Refresh / navigate without choosing**                         | Banner stays visible until user picks                        | Same                                                |
| **Click X / close icon without choosing**                       | Treated as "Reject all" — necessary only                     | Same — GDPR forbids treating it as Accept           |
| **Cookie revision bumped (policy change)**                      | Banner reappears (forced re-consent)                         | Same — Stripe/MS use version field too              |
| **Cleared browser data**                                        | Banner reappears (no cookie present)                         | Same                                                |
| **Incognito / private window**                                  | Banner appears every session                                 | Same                                                |
| **Cross-device**                                                | Each device decides independently                            | Same — consent is per-browser-storage               |
| **Browser sends Sec-GPC: 1** (Brave / DuckDuckGo / Firefox+ext) | No banner; auto-Reject (only necessary)                      | Same — Microsoft + 2024+ US state laws require this |
| **Click Privacy Policy in banner**                              | Navigate to `/privacy`; banner stays visible                 | Same — must not lose consent state mid-read         |
| **Click Cookie Preferences in footer**                          | Re-opens preferences modal                                   | Same — required by GDPR Article 7(3)                |
| **Toggle Analytics off after accepting**                        | `_ga` / `_clck` / `MUID` deleted on next page load           | Same — required by GDPR Article 7(3)                |
| **Toggle Analytics on after declining**                         | GA + Clarity load on next render                             | Same                                                |
| **Reject vs site functionality**                                | All site features keep working (login, payments, newsletter) | Same — GDPR forbids cookie-walling                  |
| **Affiliate `?ref=xxx` referral with marketing rejected**       | Cookie not stored (marketing category off)                   | Same — Stripe / Vercel apply same gating            |

## Cookie expiry rationale (13 months)

The default expiry is **395 days (\~13 months)**, matching:

* **Stripe** (13 months)
* **Microsoft** (13 months)
* **Cloudflare** (13 months)

This is the upper bound recommended by the UK ICO and France's CNIL —
long enough that returning users aren't bannered repeatedly, short
enough that consent stays demonstrably fresh. Sites going for shorter
windows (Vercel / Linear / Notion at 6 months) are also compliant; we
chose 13 to match the most rigorous reference implementations. To
adjust, edit `siteConfig.cookieConsent.expiresAfterDays`.

## Customizing the banner copy

All banner text lives in `messages/{en,zh}.json` under the
`CookieConsent` namespace. To change the title, button labels, or
descriptions, edit those keys — both files in lockstep (the i18n audit
catches drift).

## Customizing the banner UI

`vanilla-cookieconsent` exposes \~30 CSS variables for colors, radii,
shadows, and font sizes. Override them by editing the imported CSS or
adding a custom CSS layer:

```css theme={null}
:root {
  --cc-bg: #fff;
  --cc-primary-color: var(--accent-tech);  /* match vibestrap brand */
  --cc-border-radius: 0.5rem;
  /* see: https://cookieconsent.orestbida.com/advanced/customization.html */
}
```

For a complete UI override, set `disablePageInteraction: false` and
write your own Tailwind-styled container — but the default already
matches Linear / Stripe-style toasts.

## Withdrawal entry point

GDPR Article 7(3) requires that withdrawing consent be as easy as
giving it. vibestrap renders a **Cookie Preferences** link in the
footer's Legal section that re-opens the modal. This is mounted by
`src/components/cookie-consent/cookie-preferences-link.tsx` and is
automatically wired — don't remove it.

## Common pitfalls

1. **Banner doesn't show in dev.** This is intentional — dev mode
   short-circuits to "everything accepted" so you can test analytics
   without bannering yourself on every reload. Use `pnpm build &&
   pnpm start` to see the real banner.

2. **Forgot to bump revision after adding a new tracker.** Existing
   users won't be re-prompted, and they'll be tracked under their old
   consent (which didn't cover the new tracker). Always bump
   `siteConfig.cookieConsent.revision` in the same PR that adds a
   tracker.

3. **Analytics still loading after Decline.** Check that the script
   render in `src/analytics/analytics.tsx` is actually behind
   `allowAnalytics` — easy to add a new tracker and forget the gate.
   The convention: anything that drops a cookie is gated, anything
   cookie-free (Vercel / Plausible / Umami) renders unconditionally.

4. **Cookie banner shows broken HTML.** If you customized the banner
   `footer` string (links to Privacy Policy / Privacy Choices), make
   sure the HTML is valid — the lib renders it raw.

5. **Locale mismatch.** The banner reads from
   `<html lang="…">` (set by next-intl). If your custom layout sets a
   different `lang` attribute, the banner falls back to English.

## "Your Privacy Choices" — global framing

The banner footer links to `/privacy#privacy-choices`, a globally-framed
section in the privacy policy that covers opt-out rights for everyone:
GDPR (EU / UK), CCPA / CPRA (California), LGPD (Brazil), PIPL (China).
The opt-out **mechanism** is identical for every visitor (banner,
footer link, or email); the section then names each region's specific
right (e.g. California's legally-named "Do Not Sell My Personal
Information") so a regulator searching for the phrase still finds it.

This mirrors how Apple, Microsoft, and Stripe have moved away from
California-only "Do Not Sell" headers toward a global "Your Privacy
Choices" frame with named sub-rights. If you target California heavily
and want a dedicated form for opt-out requests, add a
`/privacy/privacy-choices` page; the inline section is the indie-stage
default.

## Disabling cookie consent entirely

If you're cookie-free (only running Vercel Analytics / Plausible /
Umami) and want to skip the banner, set:

```ts theme={null}
// src/config/site.ts
cookieConsent: { enable: false, revision: 1, expiresAfterDays: 365 },
```

This still mounts the React Context (so `useConsent()` keeps working),
but treats every user as having consented to everything. Skip the
banner UI entirely.

## Official resources

* vanilla-cookieconsent docs:
  [cookieconsent.orestbida.com](https://cookieconsent.orestbida.com/)
* GDPR Article 7 (consent):
  [gdpr-info.eu/art-7-gdpr](https://gdpr-info.eu/art-7-gdpr/)
* CCPA / CPRA:
  [oag.ca.gov/privacy/ccpa](https://oag.ca.gov/privacy/ccpa)
