@c15t/nextjs 2.2.0 → 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/AGENTS.md +102 -142
  2. package/README.md +4 -4
  3. package/dist/api.js +1 -0
  4. package/dist/config.js +1 -0
  5. package/dist/devtools.js +2 -0
  6. package/dist/headers.js +1 -0
  7. package/dist/iab/styles.css +1 -1
  8. package/dist/iab/styles.tw3.css +133 -19
  9. package/dist/index.js +1 -1
  10. package/dist/middleware.js +1 -0
  11. package/dist/node-bridge.js +1 -0
  12. package/dist/pages.js +1 -0
  13. package/dist/proxy.js +1 -0
  14. package/dist/root.js +2 -0
  15. package/dist/server.js +1 -0
  16. package/dist/static.js +1 -0
  17. package/dist/styles.css +1 -1
  18. package/dist/styles.tw3.css +173 -29
  19. package/dist/version.js +1 -1
  20. package/dist-types/api.d.ts +88 -0
  21. package/dist-types/config.d.ts +110 -0
  22. package/dist-types/devtools.d.ts +1 -0
  23. package/dist-types/headers.d.ts +4 -0
  24. package/dist-types/index.d.ts +28 -10
  25. package/dist-types/middleware.d.ts +15 -0
  26. package/dist-types/node-bridge.d.ts +57 -0
  27. package/dist-types/pages.d.ts +88 -0
  28. package/dist-types/proxy.d.ts +34 -0
  29. package/dist-types/root.d.ts +86 -0
  30. package/dist-types/server.d.ts +164 -0
  31. package/dist-types/static.d.ts +42 -0
  32. package/dist-types/types.d.ts +5 -36
  33. package/dist-types/version.d.ts +1 -1
  34. package/docs/README.md +102 -142
  35. package/docs/assets/v3/brand-bar.png +0 -0
  36. package/docs/assets/v3/brand-card.png +0 -0
  37. package/docs/assets/v3/choice-wall.png +0 -0
  38. package/docs/assets/v3/mobile-card.png +0 -0
  39. package/docs/assets/v3/preferences.png +0 -0
  40. package/docs/customization/overview.md +45 -0
  41. package/docs/customization/recipes.md +79 -0
  42. package/docs/customization/slots.md +55 -0
  43. package/docs/customization/tokens.md +76 -0
  44. package/docs/customization/translations.md +49 -0
  45. package/docs/frameworks/next/api-reference/data-fetching.md +416 -0
  46. package/docs/frameworks/next/app-router.md +403 -0
  47. package/docs/frameworks/next/client-side.md +118 -0
  48. package/docs/frameworks/next/components/consent-banner.md +251 -211
  49. package/docs/frameworks/next/components/consent-dialog-link.md +96 -35
  50. package/docs/frameworks/next/components/consent-dialog-trigger.md +74 -149
  51. package/docs/frameworks/next/components/consent-dialog.md +189 -134
  52. package/docs/frameworks/next/components/consent-manager-provider.md +60 -318
  53. package/docs/frameworks/next/components/consent-widget.md +172 -114
  54. package/docs/frameworks/next/components/dev-tools.md +199 -40
  55. package/docs/frameworks/next/components/frame.md +137 -42
  56. package/docs/frameworks/next/concepts/consent-categories.md +24 -89
  57. package/docs/frameworks/next/concepts/policy-presets.md +142 -0
  58. package/docs/frameworks/next/content-security-policy.md +189 -0
  59. package/docs/frameworks/next/data-fetching.md +74 -0
  60. package/docs/frameworks/next/geography-headers.md +251 -0
  61. package/docs/frameworks/next/headless.md +95 -185
  62. package/docs/frameworks/next/hooks/use-consent-manager/overview.md +42 -163
  63. package/docs/frameworks/next/iab/overview.md +37 -107
  64. package/docs/frameworks/next/optimization.md +158 -194
  65. package/docs/frameworks/next/pages-router.md +296 -0
  66. package/docs/frameworks/next/quickstart.md +31 -132
  67. package/docs/frameworks/next/script-loader.md +134 -467
  68. package/docs/frameworks/next/server-side.md +97 -130
  69. package/docs/frameworks/next/static-export.md +164 -0
  70. package/docs/frameworks/next/styling/overview.md +166 -250
  71. package/docs/frameworks/next/troubleshooting.md +134 -144
  72. package/docs/guides/consent-state.md +60 -0
  73. package/docs/guides/data-fetching.md +163 -0
  74. package/docs/guides/deployment-modes.md +63 -0
  75. package/docs/guides/troubleshooting.md +68 -0
  76. package/docs/guides/verify-consent.md +62 -0
  77. package/docs/integrations/adobe-analytics.md +239 -105
  78. package/docs/integrations/ahrefs-analytics.md +238 -104
  79. package/docs/integrations/amplitude.md +219 -157
  80. package/docs/integrations/building-integrations.md +32 -224
  81. package/docs/integrations/clearbit.md +247 -86
  82. package/docs/integrations/cloudflare-web-analytics.md +250 -84
  83. package/docs/integrations/crisp.md +251 -97
  84. package/docs/integrations/databuddy.md +259 -153
  85. package/docs/integrations/fathom-analytics.md +239 -96
  86. package/docs/integrations/google-maps.md +328 -207
  87. package/docs/integrations/google-tag-manager.md +248 -96
  88. package/docs/integrations/google-tag.md +261 -90
  89. package/docs/integrations/heap.md +222 -149
  90. package/docs/integrations/hightouch.md +225 -131
  91. package/docs/integrations/hotjar.md +239 -90
  92. package/docs/integrations/intercom.md +239 -98
  93. package/docs/integrations/linkedin-insights.md +243 -113
  94. package/docs/integrations/logrocket.md +241 -123
  95. package/docs/integrations/matomo-analytics.md +256 -111
  96. package/docs/integrations/meta-pixel.md +197 -324
  97. package/docs/integrations/microsoft-clarity.md +233 -114
  98. package/docs/integrations/microsoft-uet.md +245 -110
  99. package/docs/integrations/mixpanel-analytics.md +252 -87
  100. package/docs/integrations/openai-pixel.md +441 -0
  101. package/docs/integrations/overview.md +95 -133
  102. package/docs/integrations/pirsch.md +249 -96
  103. package/docs/integrations/plausible-analytics.md +241 -100
  104. package/docs/integrations/posthog.md +353 -214
  105. package/docs/integrations/promptwatch.md +251 -81
  106. package/docs/integrations/reddit-pixel.md +226 -173
  107. package/docs/integrations/rudderstack.md +244 -187
  108. package/docs/integrations/rybbit-analytics.md +244 -91
  109. package/docs/integrations/segment.md +238 -92
  110. package/docs/integrations/snapchat-pixel.md +240 -110
  111. package/docs/integrations/tiktok-pixel.md +249 -81
  112. package/docs/integrations/umami-analytics.md +242 -95
  113. package/docs/integrations/vercel-analytics.md +242 -90
  114. package/docs/integrations/x-pixel.md +238 -104
  115. package/docs/integrations/youtube.md +354 -142
  116. package/docs/upgrade-v3.md +334 -0
  117. package/iab/styles.css +1 -1
  118. package/iab/styles.tw3.css +1 -1
  119. package/package.json +106 -65
  120. package/readme.json +3 -3
  121. package/src/iab/styles.css +1 -1
  122. package/src/iab/styles.tw3.css +1 -1
  123. package/src/styles.css +1 -1
  124. package/src/styles.tw3.css +1 -1
  125. package/styles.css +1 -1
  126. package/styles.tw3.css +1 -1
  127. package/client/components/consent-dialog-link.js +0 -3
  128. package/client/components/integrations.js +0 -3
  129. package/dist/components/integrations/index.cjs +0 -1
  130. package/dist/components/integrations/index.js +0 -1
  131. package/dist/headless.cjs +0 -1
  132. package/dist/index.cjs +0 -1
  133. package/dist/libs/browser-initial-data.cjs +0 -1
  134. package/dist/libs/browser-initial-data.js +0 -1
  135. package/dist/libs/initial-data.cjs +0 -1
  136. package/dist/libs/initial-data.js +0 -1
  137. package/dist/types.cjs +0 -1
  138. package/dist/version.cjs +0 -1
  139. package/dist-types/components/integrations/index.d.ts +0 -1
  140. package/dist-types/libs/browser-initial-data.d.ts +0 -9
  141. package/dist-types/libs/initial-data.d.ts +0 -33
  142. package/docs/frameworks/next/building-headless-components.md +0 -379
  143. package/docs/frameworks/next/callbacks.md +0 -186
  144. package/docs/frameworks/next/concepts/client-modes.md +0 -177
  145. package/docs/frameworks/next/concepts/consent-models.md +0 -117
  146. package/docs/frameworks/next/concepts/cookie-management.md +0 -122
  147. package/docs/frameworks/next/concepts/glossary.md +0 -24
  148. package/docs/frameworks/next/concepts/initialization-flow.md +0 -149
  149. package/docs/frameworks/next/concepts/policy-packs.md +0 -230
  150. package/docs/frameworks/next/hooks/use-color-scheme.md +0 -41
  151. package/docs/frameworks/next/hooks/use-consent-manager/checking-consent.md +0 -96
  152. package/docs/frameworks/next/hooks/use-consent-manager/location-info.md +0 -97
  153. package/docs/frameworks/next/hooks/use-consent-manager/setting-consent.md +0 -94
  154. package/docs/frameworks/next/hooks/use-draggable.md +0 -59
  155. package/docs/frameworks/next/hooks/use-focus-trap.md +0 -42
  156. package/docs/frameworks/next/hooks/use-reduced-motion.md +0 -37
  157. package/docs/frameworks/next/hooks/use-ssr-status.md +0 -32
  158. package/docs/frameworks/next/hooks/use-text-direction.md +0 -50
  159. package/docs/frameworks/next/hooks/use-translations.md +0 -55
  160. package/docs/frameworks/next/iab/consent-banner.md +0 -91
  161. package/docs/frameworks/next/iab/consent-dialog.md +0 -129
  162. package/docs/frameworks/next/iab/use-gvl-data.md +0 -21
  163. package/docs/frameworks/next/iframe-blocking.md +0 -106
  164. package/docs/frameworks/next/internationalization.md +0 -207
  165. package/docs/frameworks/next/network-blocker.md +0 -140
  166. package/docs/frameworks/next/policy-packs.md +0 -248
  167. package/docs/frameworks/next/styling/classnames.md +0 -94
  168. package/docs/frameworks/next/styling/color-scheme.md +0 -84
  169. package/docs/frameworks/next/styling/css-variables.md +0 -53
  170. package/docs/frameworks/next/styling/slots.md +0 -94
  171. package/docs/frameworks/next/styling/tailwind.md +0 -137
  172. package/docs/frameworks/next/styling/tokens.md +0 -156
  173. package/docs/shared/concepts/client-modes.md +0 -103
  174. package/docs/shared/concepts/consent-categories.md +0 -41
  175. package/docs/shared/concepts/consent-models.md +0 -72
  176. package/docs/shared/concepts/cookie-management.md +0 -88
  177. package/docs/shared/concepts/glossary.md +0 -24
  178. package/docs/shared/concepts/initialization-flow.md +0 -105
  179. package/docs/shared/concepts/policy-packs.md +0 -225
  180. package/docs/shared/react/components/consent-banner.md +0 -242
  181. package/docs/shared/react/components/consent-dialog-link.md +0 -45
  182. package/docs/shared/react/components/consent-dialog-trigger.md +0 -185
  183. package/docs/shared/react/components/consent-dialog.md +0 -119
  184. package/docs/shared/react/components/consent-manager-provider.md +0 -225
  185. package/docs/shared/react/components/consent-widget.md +0 -121
  186. package/docs/shared/react/components/dev-tools.md +0 -81
  187. package/docs/shared/react/components/frame.md +0 -52
  188. package/docs/shared/react/guides/building-headless-components.md +0 -110
  189. package/docs/shared/react/guides/callbacks.md +0 -89
  190. package/docs/shared/react/guides/headless.md +0 -31
  191. package/docs/shared/react/guides/iframe-blocking.md +0 -65
  192. package/docs/shared/react/guides/internationalization.md +0 -123
  193. package/docs/shared/react/guides/network-blocker.md +0 -72
  194. package/docs/shared/react/guides/optimization.md +0 -44
  195. package/docs/shared/react/guides/policy-packs.md +0 -173
  196. package/docs/shared/react/guides/script-loader.md +0 -311
  197. package/docs/shared/react/hooks/use-color-scheme.md +0 -31
  198. package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +0 -95
  199. package/docs/shared/react/hooks/use-consent-manager/location-info.md +0 -96
  200. package/docs/shared/react/hooks/use-consent-manager/overview.md +0 -74
  201. package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +0 -93
  202. package/docs/shared/react/hooks/use-draggable.md +0 -30
  203. package/docs/shared/react/hooks/use-focus-trap.md +0 -20
  204. package/docs/shared/react/hooks/use-reduced-motion.md +0 -33
  205. package/docs/shared/react/hooks/use-ssr-status.md +0 -16
  206. package/docs/shared/react/hooks/use-text-direction.md +0 -38
  207. package/docs/shared/react/hooks/use-translations.md +0 -15
  208. package/docs/shared/react/iab/consent-banner.md +0 -60
  209. package/docs/shared/react/iab/consent-dialog.md +0 -76
  210. package/docs/shared/react/iab/overview.md +0 -80
  211. package/docs/shared/react/iab/use-gvl-data.md +0 -21
  212. package/docs/shared/react/styling/classnames.md +0 -93
  213. package/docs/shared/react/styling/color-scheme.md +0 -35
  214. package/docs/shared/react/styling/css-variables.md +0 -53
  215. package/docs/shared/react/styling/overview.md +0 -261
  216. package/docs/shared/react/styling/slots.md +0 -93
  217. package/docs/shared/react/styling/stylesheet-entrypoint.md +0 -8
  218. package/docs/shared/react/styling/tailwind.md +0 -88
  219. package/docs/shared/react/styling/tokens.md +0 -155
  220. package/docs/shared/troubleshooting.md +0 -82
@@ -1,88 +0,0 @@
1
- ---
2
- title: Cookie Management
3
- description: Reference page for cookie management.
4
- group: reference
5
- ---
6
- Cookie management is the most commonly misunderstood part of consent. A critical distinction: **c15t does not manage all cookies on your site.** It controls what scripts, iframes, and network requests are allowed to load - and those third-party resources are what set most cookies.
7
-
8
- Understanding this distinction is key to building a compliant consent flow: c15t gates the sources of cookies, not the cookies themselves.
9
-
10
- ## What Actually Sets Cookies
11
-
12
- There are four sources of cookies on a typical website:
13
-
14
- **1. c15t itself**
15
- c15t stores the user's consent state in a `c15t` cookie (and mirrors it to localStorage). This cookie records which categories the user consented to, when they consented, and their subject ID. This is a strictly necessary cookie - it exists so the site remembers the user's consent choice.
16
-
17
- **2. Third-party scripts**
18
- When you load a tracking script (Google Analytics, Meta Pixel, etc.), that script sets its own cookies. Google Analytics creates `_ga` and `_gid` cookies. Meta creates `_fbp` and `_fbc`. These cookies are set by the script's code running in the browser - c15t prevents them by not loading the script until the user consents.
19
-
20
- **3. Embedded iframes**
21
- YouTube embeds, social media widgets, and other iframes can set cookies via their embedded content. c15t's iframe blocker replaces iframes with placeholders until consent is granted, preventing these cookies from being set.
22
-
23
- **4. Network requests**
24
- Server responses can include `Set-Cookie` headers. If your page makes requests to third-party APIs or CDNs, those responses may set cookies. c15t's network blocker can intercept `fetch` and `XMLHttpRequest` calls to prevent these requests from happening without consent.
25
-
26
- > ⚠️ **Warning:**
27
- > Don't list these cookies in your banner. The cookie names above (\_ga, \_gid, \_fbp, \_fbc) are implementation details — they matter for developers understanding how tracking works, but they should not be exposed to end users in your consent UI. Users don't know what \_ga means, and listing it doesn't help them make an informed choice. Instead, use purpose-based consent categories like "measurement" or "marketing" that communicate why data is collected, not how it is stored.
28
-
29
- ## Why Revoking Consent Requires a Page Reload
30
-
31
- When a user revokes consent for a category (e.g., turns off "measurement" after previously granting it), c15t reloads the page by default. This is not a limitation - it's the only reliable approach.
32
-
33
- **Why you can't just delete third-party cookies from JavaScript:**
34
-
35
- * **`httpOnly` cookies** - Many tracking cookies are set with the `httpOnly` flag, which prevents JavaScript from reading or deleting them. Only the server that set them can remove them.
36
- * **Domain restrictions** - Cookies set on `.google.com` or `.facebook.com` can only be deleted by those domains. Your JavaScript running on `yourdomain.com` has no access.
37
- * **Alternative storage** - Some scripts also write to `localStorage`, `sessionStorage`, `IndexedDB`, or even Web Workers. Cleaning up all possible storage locations is impractical.
38
- * **In-memory state** - A loaded script has already executed. Its event listeners, timers, and in-memory data persist until the page unloads. You can't "unrun" JavaScript.
39
-
40
- **The reliable solution: don't load the scripts in the first place.** A page reload creates a fresh execution context. On the fresh page, c15t reads the updated consent state and simply never loads the scripts that lost consent. No script means no cookies, no tracking, no in-memory state.
41
-
42
- **When reload does NOT happen:**
43
-
44
- * When a user is declining consent for the **first time** (no prior consent existed, so no scripts were loaded to clean up)
45
- * When `reloadOnConsentRevoked` is set to `false`
46
- * When the user is only **adding** consent (no revocations)
47
-
48
- ## The Revocation Flow
49
-
50
- When a user revokes consent, the following sequence occurs:
51
-
52
- **Simplified**
53
-
54
- 1. **User revokes consent** — e.g. turns off "measurement" in the consent dialog
55
- 2. **New consent saved** — updated preferences are written to cookies and localStorage
56
- 3. **Pending sync stored** — the API update is deferred to localStorage (`c15t:pending-consent-sync`)
57
- 4. **Page reloads** — a fresh execution context ensures revoked scripts never load
58
- 5. **Fresh init** — c15t reads updated consent; scripts without consent are never loaded
59
- 6. **Deferred API sync** — the pending consent change is sent to the backend and cleared from localStorage
60
-
61
- **Sequence Diagram**
62
-
63
- ```mermaid
64
- sequenceDiagram
65
- participant User
66
- participant UI as Consent UI
67
- participant Store as Consent Store
68
- participant Storage as Cookie + localStorage
69
- participant API as c15t Backend
70
- participant Browser
71
-
72
- User->>UI: Revokes "measurement" consent
73
- UI->>Store: saveConsents({ type: 'custom' })
74
- Store->>Store: shouldReloadOnConsentChange() → true
75
- Store->>Storage: Save new consent state
76
- Store->>Storage: Store PendingConsentSync in localStorage
77
- Store->>Browser: onBeforeConsentRevocationReload callback
78
- Store->>Browser: window.location.reload()
79
- Browser->>Browser: Fresh page load
80
- Browser->>Storage: Read consent state
81
- Browser->>Store: Initialize with updated consents
82
- Note over Store: Scripts without consent are never loaded
83
- Store->>Storage: Read PendingConsentSync
84
- Store->>API: Sync consent to backend
85
- Store->>Storage: Clear PendingConsentSync
86
- ```
87
-
88
- **Key detail:** The API sync happens *after* the reload, not before. This ensures the page reloads as fast as possible. The pending sync data is stored in localStorage under the key `c15t:pending-consent-sync` and is picked up by the fresh page's initialization.
@@ -1,24 +0,0 @@
1
- ---
2
- title: Glossary
3
- description: Reference page for glossary.
4
- group: reference
5
- ---
6
- ### Core Terms
7
-
8
- |Term|Definition|
9
- |--|--|
10
- |`subjectId`|Client-generated device/browser identifier (`sub_xxx`). Stored in the `c15t` cookie. Created on first consent save.|
11
- |`externalId`|Your authenticated user's ID (from Clerk, Auth0, etc.), linked via `identifyUser()`. Connects a device to a user account.|
12
- |`consents`|The **saved** consent state — `Record<string, boolean>` mapping categories to granted/denied. Used for gating scripts, iframes, and network requests.|
13
- |`selectedConsents`|**Unsaved** toggle state — what the user has toggled in the dialog but hasn't submitted yet. Becomes `consents` after save.|
14
- |`mode`|Client operating mode: `'hosted'` (full backend), `'offline'` (local only), or `'custom'` (bring your own handlers). Legacy alias: `'c15t'`. Set once in provider config. See [Client Modes](/docs/frameworks/react/concepts/client-modes).|
15
- |`model`|Consent regulatory model derived from jurisdiction: `'opt-in'`, `'opt-out'`, `'iab'`, or `null`. See [Consent Models](/docs/frameworks/react/concepts/consent-models).|
16
-
17
- ### Regulatory Terms
18
-
19
- |Term|Definition|
20
- |--|--|
21
- |`jurisdiction`|A privacy regulation code (e.g., `GDPR`, `CCPA`, `PIPEDA`, `QC_LAW25`) detected from the user's geolocation. Maps to a consent model. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for the full mapping table.|
22
- |GPC|Global Privacy Control — a browser signal (`Sec-GPC: 1`) indicating the user opts out of data sale/sharing. Honored when a policy sets `consent.gpc: true` (enabled by default in California presets).|
23
- |TCF|IAB Transparency and Consent Framework (v2.3) — a standard for programmatic advertising consent in GDPR jurisdictions. See [IAB TCF](/docs/frameworks/react/iab/overview).|
24
- |GVL|Global Vendor List — an IAB-maintained registry of ad-tech vendors and their declared purposes, used by the TCF consent flow.|
@@ -1,105 +0,0 @@
1
- ---
2
- title: Initialization Flow
3
- description: Reference page for initialization flow.
4
- group: reference
5
- ---
6
- When the consent provider mounts, it creates a cached consent runtime, reads any stored consent from the browser, fetches the resolved policy from the backend (or uses SSR/offline data), and decides whether to show the banner. This entire sequence completes before the first meaningful consent-aware render.
7
-
8
- ## Lifecycle Sequence
9
-
10
- **Simplified**
11
-
12
- 1. **Provider mounts** — creates (or retrieves from cache) a consent runtime and store
13
- 2. **Check stored consent** — reads existing consent from cookies / localStorage; if found and the policy fingerprint hasn't changed, the banner stays hidden
14
- 3. **Fetch init data** — calls the backend `GET /init` (or uses SSR/offline data) for the resolved policy, location, and translations
15
- 4. **Apply resolved policy** — the backend resolves the policy from your [policy pack](/docs/frameworks/react/concepts/policy-packs) based on visitor geo (region → country → fallback → default). The response includes the consent model, categories, UI mode, and a material fingerprint. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead.
16
- 5. **Decide banner visibility** — shows the banner only if no prior consent exists, the resolved policy requires it (`ui.mode` is `banner` or `dialog`), or the policy fingerprint changed since last consent
17
- 6. **Gating enforced** — scripts, iframes, and network requests tagged with a consent category are blocked until that category is granted
18
- 7. **User interacts** — choices are persisted to storage, synced to the backend (with `policySnapshotToken` if configured), and blocked scripts/iframes load immediately after consent is granted
19
-
20
- **Sequence Diagram**
21
-
22
- ```mermaid
23
- sequenceDiagram
24
- participant Provider as ConsentManagerProvider
25
- participant Store as Consent Store
26
- participant Storage as localStorage / Cookie
27
- participant API as c15t Backend
28
- participant UI as Banner / Dialog
29
- participant Scripts as Script Loader
30
-
31
- Provider->>Store: getOrCreateConsentRuntime()
32
- Store->>Storage: getStoredConsent()
33
- alt Stored consent exists
34
- Storage-->>Store: consentInfo + consents
35
- Store->>Store: activeUI = 'none'
36
- else No stored consent
37
- Store->>Store: isLoadingConsentInfo = true
38
- end
39
-
40
- Store->>Store: initConsentManager()
41
- Store->>Storage: Check pending consent sync
42
- opt Pending sync from revocation reload
43
- Store->>API: Deferred setConsent() (non-blocking)
44
- end
45
-
46
- alt SSR data provided
47
- Store->>Store: tryUseSSRData()
48
- else No SSR data
49
- Store->>API: GET /init
50
- API-->>Store: policy, policyDecision, location, translations
51
- end
52
-
53
- Store->>Store: Apply resolved policy (model, categories, ui.mode)
54
- Store->>Store: Check fingerprint change → re-prompt if needed
55
- Store->>Store: Set activeUI, auto-grant if opt-out/none
56
-
57
- alt User has no prior consent or policy changed
58
- UI->>UI: Banner / Dialog appears (per policy ui.mode)
59
- UI->>Store: saveConsents({ type })
60
- Store->>Storage: Persist consent + subjectId + fingerprint
61
- Store->>Scripts: updateScripts(), updateIframes(), updateNetwork()
62
- Store->>API: POST /subjects + policySnapshotToken (non-blocking)
63
- end
64
- ```
65
-
66
- ## How It Works
67
-
68
- **Mount** — When the provider renders, it creates (or retrieves from cache) a consent runtime and store. Any existing consent is read from localStorage/cookies immediately. If consent already exists and the policy fingerprint matches, the banner stays hidden and gating rules apply right away. See [Client Modes](/docs/frameworks/react/concepts/client-modes) for how the mode affects runtime creation.
69
-
70
- **Init** — The store fetches the resolved policy, location, and translation data. In hosted mode this calls `GET /init` on your backend; in offline mode it resolves from `offlinePolicy.policyPacks` locally. If SSR data was passed to the provider, the network fetch is skipped entirely. See [Server-Side Utilities](/docs/frameworks/react/server-side) for SSR setup.
71
-
72
- **Policy resolution** — When [policy packs](/docs/frameworks/react/concepts/policy-packs) are configured, the backend resolves the right policy for the visitor based on their geo-location (region → country → fallback → default). The resolved policy determines the consent model (`opt-in`, `opt-out`, `iab`, or `none`), which categories are in scope, and what UI to show. For `opt-out` and `none` models, all categories are auto-granted — unless the resolved policy has `consent.gpc: true` and the browser sends a Global Privacy Control signal, in which case `marketing` and `measurement` are denied. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for details.
73
-
74
- **Re-prompting** — If the resolved policy's material fingerprint differs from the fingerprint stored with the user's last consent, the banner is shown again. This happens automatically when you change consent-affecting fields (model, categories, scope mode, allowed actions). Presentation-only changes do not trigger re-prompts. See [Policy Packs — Re-Prompting](/docs/frameworks/react/concepts/policy-packs#re-prompting) for details.
75
-
76
- **Save** — When the user interacts with the banner or dialog, their choices are persisted to localStorage/cookies and synced to the backend (along with the `policySnapshotToken` if snapshot signing is configured). Script, iframe, and network gating rules update immediately based on the new consent state. See the [Script Loader](/docs/frameworks/react/script-loader), [Iframe Blocking](/docs/frameworks/react/iframe-blocking), and [Network Blocker](/docs/frameworks/react/network-blocker) guides for gating details.
77
-
78
- **Revocation** — If a user revokes a previously granted category, the page reloads by default to ensure a clean execution environment. The API sync is deferred to the fresh page load. See [Cookie Management](/docs/frameworks/react/concepts/cookie-management) for revocation and persistence details.
79
-
80
- ## When Does the Banner Show?
81
-
82
- The banner appears when any of these conditions are true:
83
-
84
- 1. **No existing consent** — the user has never consented (or their consent was cleared), **and** the resolved policy requires a UI (`ui.mode` is `banner` or `dialog`, or the model is `opt-in` or `iab`)
85
- 2. **Policy changed** — the material policy fingerprint differs from the fingerprint stored with the user's last consent (re-prompting)
86
- 3. **Storage is accessible** — the browser allows localStorage (not blocked in private mode)
87
-
88
- If the resolved model is `none` or `opt-out` (and `ui.mode` is `none`), consents are auto-granted and the banner never appears. See [Consent Models](/docs/frameworks/react/concepts/consent-models) and [Policy Packs](/docs/frameworks/react/concepts/policy-packs) for details.
89
-
90
- ## Debugging the Lifecycle
91
-
92
- Use the DevTools panel and callbacks to inspect each step of the initialization flow. `onConsentSet` is the broad lifecycle signal; `onConsentChanged` and `subscribeToConsentChanges()` are the change-only signals for explicit post-init saves.
93
-
94
- |Step|DevTools Panel|Callback|What to check|
95
- |--|--|--|--|
96
- |Init / SSR hydration|Location|`onBannerFetched`|jurisdiction, countryCode, regionCode populated?|
97
- |Policy resolution|Policy|`onBannerFetched`|`policyId`, `matchedBy`, `fingerprint` in policyDecision|
98
- |Model resolution|Location|`onBannerFetched`|`model` value matches the resolved policy|
99
- |Banner visibility|Consents|—|`activeUI` in store state; does policy `ui.mode` require it?|
100
- |Re-prompting|Policy|—|Fingerprint mismatch between stored and resolved policy?|
101
- |Consent save|Consents + Events|`onConsentSet`|`preferences` object in callback payload|
102
- |Change-only integrations|Events|`onConsentChanged` or `subscribeToConsentChanges()`|`allowedCategories`, `deniedCategories`, and previous values only when a real save changed preferences|
103
- |Script loading|Scripts|`onConsentSet`|Script IDs and their load/blocked status|
104
- |Reload on revocation|Events|`onBeforeConsentRevocationReload`|Fires before reload; check localStorage for `c15t:pending-consent-sync`|
105
- |Deferred sync|Events|`onError` (if sync fails)|After reload, check Events panel for successful API call|
@@ -1,225 +0,0 @@
1
- ---
2
- title: Policy Packs
3
- description: Reference page for policy packs.
4
- group: reference
5
- ---
6
- Different countries need different consent experiences. Policy packs let you define those rules once — c15t picks the right one automatically based on where the visitor is.
7
-
8
- A policy pack is an ordered array of policies. Each policy targets a region or country and controls the consent model, which categories are in scope, what UI is shown, and how consent is recorded.
9
-
10
- There are three ways to configure policy packs:
11
-
12
- 1. **inth.com (recommended)** — use [inth.com](https://inth.com) as your hosted backend. Configure packs visually in the dashboard or via API — no code changes required. Works with any frontend, including static sites.
13
- 2. **Self-hosted backend** — define packs in code via `policyPacks` and resolve them from real request geo data. Full control over policy logic and storage.
14
- 3. **Offline fallback** — pass the same policy shapes to the frontend via `offlinePolicy.policyPacks`. Use this mainly for local development, demos, deterministic testing, or resilience when the backend is temporarily unreachable. If you omit `offlinePolicy.policyPacks`, c15t falls back to a synthetic worldwide opt-in banner instead of no-banner mode.
15
-
16
- In both hosted and self-hosted modes, the **backend is always the source of truth**. Offline packs are a preview or fallback layer and never override a live backend decision.
17
-
18
- ## Quickstart
19
-
20
- The fastest way to get started is with the built-in presets:
21
-
22
- ```ts
23
- import { policyPackPresets } from 'c15t';
24
-
25
- const policies = [
26
- policyPackPresets.europeOptIn(), // GDPR opt-in banner
27
- policyPackPresets.californiaOptOut(), // CCPA opt-out banner
28
- policyPackPresets.worldNoBanner(), // No banner elsewhere
29
- ];
30
- ```
31
-
32
- |Preset|Model|UI|Matches|
33
- |--|--|--|--|
34
- |`europeOptIn()`|`opt-in`|banner|EEA + UK countries + geo fallback|
35
- |`europeIab()`|`iab`|banner|EEA + UK countries + geo fallback (TCF 2.3)|
36
- |`californiaOptOut()`|`opt-out`|none|US-CA region|
37
- |`quebecOptIn()`|`opt-in`|banner|CA-QC region|
38
- |`worldNoBanner()`|`none`|none|default fallback|
39
-
40
- Most apps only need these presets — pick the ones that match your regions, pass them to your backend config or provider, and you're done. Customize individual fields or write fully custom policies when you need more control.
41
-
42
- For banner/dialog actions, policy packs can also control grouped button arrangement:
43
-
44
- ```ts
45
- ui: {
46
- mode: 'banner',
47
- banner: {
48
- allowedActions: ['reject', 'accept', 'customize'],
49
- layout: [['reject', 'accept'], 'customize'],
50
- direction: 'row',
51
- primaryActions: ['accept', 'customize'],
52
- },
53
- }
54
- ```
55
-
56
- That expresses arrangement only. Button appearance like `stroke`, `filled`, or `ghost` lives in the UI theme.
57
-
58
- ## What Users See
59
-
60
- Each policy combination produces a different consent experience:
61
-
62
- |Policy Config|User Experience|
63
- |--|--|
64
- |`model: 'opt-in'`, `ui.mode: 'banner'`|Banner appears, nothing loads until the user consents|
65
- |`model: 'opt-out'`, `ui.mode: 'none'`|No banner, everything loads immediately — user opts out via a "Do Not Sell" link|
66
- |`model: 'none'`, `ui.mode: 'none'`|No banner, all categories auto-granted silently|
67
- |`model: 'opt-in'`, `ui.mode: 'dialog'`|Full-screen dialog, nothing loads until the user consents|
68
- |`model: 'iab'`, `ui.mode: 'banner'`|IAB TCF 2.3 banner with vendor-level controls|
69
-
70
- ## How Policy Resolution Works
71
-
72
- When a visitor arrives, c15t walks the policy pack in priority order:
73
-
74
- 1. **Match by region** — checks for a policy targeting the specific region (e.g., US-CA, CA-QC)
75
- 2. **Match by country** — if no region match, checks for a country-level policy (e.g., US, DE)
76
- 3. **Fallback (geo failure)** — if geo-location failed (no country detected), uses the policy marked with `match.fallback`
77
- 4. **Fall back to default** — if nothing matches, uses the policy marked as the default
78
- 5. **No match, no default** — resolves to no-banner mode (silent, no consent UI)
79
-
80
- Within the same matcher type, the first policy in the array wins. Pack order matters when two policies target the same country or region.
81
-
82
- The **fallback** step is distinct from **default**: `isDefault` is a catch-all for known locations that don't match any specific policy ("rest of world"), while `fallback` is a safety net for unknown locations when geo-headers are missing ("assume strictest"). The `europeOptIn()` and `europeIab()` presets include `fallback: true` by default so EU-level consent applies when geo fails.
83
-
84
- > ⚠️ **Warning:**
85
- > Only one default and one fallback policy are allowed. Use inspectPolicies() to surface overlapping matchers and other warnings before deployment.
86
-
87
- ## Common Patterns
88
-
89
- **The 80% case** — strict in Europe, light in California, silent everywhere else:
90
-
91
- ```ts
92
- const policies = [
93
- {
94
- id: 'eu',
95
- match: { countries: ['DE', 'FR', 'IT'] },
96
- consent: { model: 'opt-in', categories: ['necessary', 'measurement', 'marketing'] },
97
- ui: { mode: 'banner' },
98
- },
99
- {
100
- id: 'ca',
101
- match: { regions: [{ country: 'US', region: 'CA' }] },
102
- consent: { model: 'opt-out', gpc: true },
103
- ui: { mode: 'none' },
104
- },
105
- {
106
- id: 'default',
107
- match: { isDefault: true },
108
- consent: { model: 'none' },
109
- ui: { mode: 'none' },
110
- },
111
- ];
112
- ```
113
-
114
- **Region overrides country** — stricter rules for California than the rest of the US:
115
-
116
- ```ts
117
- const policies = [
118
- {
119
- id: 'us_ca',
120
- match: { regions: [{ country: 'US', region: 'CA' }] },
121
- consent: { model: 'opt-in', scopeMode: 'strict' },
122
- ui: { mode: 'banner' },
123
- },
124
- {
125
- id: 'us',
126
- match: { countries: ['US'] },
127
- consent: { model: 'opt-out' },
128
- ui: { mode: 'banner' },
129
- },
130
- ];
131
- // US-CA → us_ca (region match wins)
132
- // US-NY → us (country match)
133
- ```
134
-
135
- **Different wording per region** — use `i18n.messageProfile` to vary copy without changing the consent model:
136
-
137
- ```ts
138
- const c15t = c15tInstance({
139
- i18n: {
140
- defaultProfile: 'default',
141
- messages: {
142
- default: {
143
- translations: {
144
- en: { cookieBanner: { title: 'Privacy choices' } },
145
- es: { cookieBanner: { title: 'Tus opciones de privacidad' } },
146
- },
147
- },
148
- eu: {
149
- fallbackLanguage: 'en',
150
- translations: {
151
- en: { cookieBanner: { title: 'EU GDPR Consent' } },
152
- fr: { cookieBanner: { title: 'Consentement RGPD' } },
153
- de: { cookieBanner: { title: 'GDPR-Einwilligung' } },
154
- },
155
- },
156
- },
157
- },
158
- policyPacks: [
159
- {
160
- id: 'eu',
161
- match: { countries: ['DE', 'FR', 'IT'] },
162
- i18n: { messageProfile: 'eu' },
163
- consent: { model: 'opt-in' },
164
- ui: { mode: 'banner' },
165
- },
166
- ],
167
- });
168
- ```
169
-
170
- In that setup, the `eu` policy uses only the `eu` language set. So Europe can
171
- resolve to `en`, `fr`, or `de`, but not to `es` or any other locale defined
172
- only in `default`. If the browser asks for an unsupported locale, c15t falls
173
- back to the `eu` profile's `fallbackLanguage`.
174
-
175
- ## Re-Prompting on Policy Change
176
-
177
- When you change a policy in a way that affects consent semantics — like adding a category, changing the consent model, or modifying allowed actions — c15t automatically re-prompts returning users.
178
-
179
- This works through the **material policy fingerprint**: a hash of only the consent-affecting fields (model, categories, scope, allowed actions, grouped action layout, direction, proof settings). Presentation-only changes like copy, button styling, or scroll lock do not trigger re-prompts.
180
-
181
- |Change|Re-prompts?|
182
- |--|--|
183
- |Add a consent category|Yes|
184
- |Change `model` from `opt-out` to `opt-in`|Yes|
185
- |Remove an `allowedAction`|Yes|
186
- |Change `uiProfile` or button styling|No|
187
- |Update translation copy|No|
188
- |Change `scrollLock`|No|
189
-
190
- ## Global Privacy Control (GPC)
191
-
192
- Each policy can opt in to respecting the [Global Privacy Control](https://globalprivacycontrol.org/) signal via `consent.gpc`:
193
-
194
- ```ts
195
- {
196
- id: 'california',
197
- match: { regions: [{ country: 'US', region: 'CA' }] },
198
- consent: { model: 'opt-out', gpc: true },
199
- }
200
- ```
201
-
202
- When `gpc: true` and the visitor's browser sends a GPC signal (`Sec-GPC: 1` header or `navigator.globalPrivacyControl`), `marketing` and `measurement` categories are automatically denied during auto-granting — honoring the user's opt-out preference.
203
-
204
- When `gpc` is `false` or omitted, the GPC signal is ignored for that policy. This is the right default for GDPR/EEA policies where consent is already opt-in and GPC is redundant.
205
-
206
- |Preset|`gpc`|Why|
207
- |--|--|--|
208
- |`californiaOptIn()`|`true`|CCPA recognizes GPC as a legal opt-out|
209
- |`californiaOptOut()`|`true`|CCPA recognizes GPC as a legal opt-out|
210
- |`europeOptIn()`|—|GDPR already requires opt-in|
211
- |`europeIab()`|—|TCF handles consent signals|
212
- |`worldNoBanner()`|—|No regulation applies|
213
-
214
- > ℹ️ **Info:**
215
- > The overrides.gpc option on the provider and the DevTools GPC toggle still work as a manual override for testing — they force the GPC signal on or off regardless of policy settings.
216
-
217
- ## Design Guidelines
218
-
219
- * **Start from presets.** Use `policyPackPresets` to get running, then customize for your needs.
220
- * **Keep packs small.** A handful of regional policies is better than dozens of tiny fragments.
221
- * **Think risk, not geography.** Geography is just a matcher — the real question is what consent behavior each region needs.
222
- * **Always include a default.** Unless "no banner for unmatched traffic" is intentional.
223
- * **Set a fallback for geo failures.** Mark your strictest policy with `match.fallback=true` so users in unknown locations still see a consent banner. The `europeOptIn()` and `europeIab()` presets do this automatically.
224
- * **Keep policy IDs stable.** They appear in debugging output, snapshots, and audit records.
225
- * **Use `inspectPolicies()` before deploying.** It catches overlapping matchers, missing defaults, and IAB misconfigurations.