@c15t/nextjs 2.2.1 → 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,230 +0,0 @@
1
- ---
2
- title: Policy Packs
3
- description: How c15t resolves regional consent policies and what a policy pack controls.
4
- group: frameworks
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
- Inspect the resolved policy from any client component:
88
-
89
- ```tsx
90
- 'use client';
91
-
92
- import { useConsentManager } from '@c15t/nextjs';
93
-
94
- export function PolicyDebug() {
95
- const { locationInfo, model, policy, policyDecision } = useConsentManager();
96
-
97
- return (
98
- <pre>
99
- {JSON.stringify(
100
- {
101
- country: locationInfo?.countryCode,
102
- region: locationInfo?.regionCode,
103
- model,
104
- policyId: policy?.id,
105
- matchedBy: policyDecision?.matchedBy,
106
- fingerprint: policyDecision?.fingerprint,
107
- },
108
- null,
109
- 2
110
- )}
111
- </pre>
112
- );
113
- }
114
- ```
115
-
116
- ## Common Patterns
117
-
118
- **The 80% case** — strict in Europe, light in California, silent everywhere else:
119
-
120
- ```ts
121
- const policies = [
122
- {
123
- id: 'eu',
124
- match: { countries: ['DE', 'FR', 'IT'] },
125
- consent: { model: 'opt-in', categories: ['necessary', 'measurement', 'marketing'] },
126
- ui: { mode: 'banner' },
127
- },
128
- {
129
- id: 'ca',
130
- match: { regions: [{ country: 'US', region: 'CA' }] },
131
- consent: { model: 'opt-out', gpc: true },
132
- ui: { mode: 'none' },
133
- },
134
- {
135
- id: 'default',
136
- match: { isDefault: true },
137
- consent: { model: 'none' },
138
- ui: { mode: 'none' },
139
- },
140
- ];
141
- ```
142
-
143
- **Region overrides country** — stricter rules for California than the rest of the US:
144
-
145
- ```ts
146
- const policies = [
147
- {
148
- id: 'us_ca',
149
- match: { regions: [{ country: 'US', region: 'CA' }] },
150
- consent: { model: 'opt-in', scopeMode: 'strict' },
151
- ui: { mode: 'banner' },
152
- },
153
- {
154
- id: 'us',
155
- match: { countries: ['US'] },
156
- consent: { model: 'opt-out' },
157
- ui: { mode: 'banner' },
158
- },
159
- ];
160
- // US-CA → us_ca (region match wins)
161
- // US-NY → us (country match)
162
- ```
163
-
164
- **Different wording per region** — use `i18n.messageProfile` to vary copy without changing the consent model:
165
-
166
- ```ts
167
- const c15t = c15tInstance({
168
- i18n: {
169
- defaultProfile: 'default',
170
- messages: {
171
- default: {
172
- translations: {
173
- en: { cookieBanner: { title: 'Privacy choices' } },
174
- es: { cookieBanner: { title: 'Tus opciones de privacidad' } },
175
- },
176
- },
177
- eu: {
178
- fallbackLanguage: 'en',
179
- translations: {
180
- en: { cookieBanner: { title: 'EU GDPR Consent' } },
181
- fr: { cookieBanner: { title: 'Consentement RGPD' } },
182
- de: { cookieBanner: { title: 'GDPR-Einwilligung' } },
183
- },
184
- },
185
- },
186
- },
187
- policyPacks: [
188
- {
189
- id: 'eu',
190
- match: { countries: ['DE', 'FR', 'IT'] },
191
- i18n: { messageProfile: 'eu' },
192
- consent: { model: 'opt-in' },
193
- ui: { mode: 'banner' },
194
- },
195
- ],
196
- });
197
- ```
198
-
199
- In that setup, the `eu` policy uses only the `eu` language set. So Europe can
200
- resolve to `en`, `fr`, or `de`, but not to `es` or any other locale defined
201
- only in `default`. If the browser asks for an unsupported locale, c15t falls
202
- back to the `eu` profile's `fallbackLanguage`.
203
-
204
- ## Re-Prompting on Policy Change
205
-
206
- 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.
207
-
208
- 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.
209
-
210
- |Change|Re-prompts?|
211
- |--|--|
212
- |Add a consent category|Yes|
213
- |Change `model` from `opt-out` to `opt-in`|Yes|
214
- |Remove an `allowedAction`|Yes|
215
- |Change `uiProfile` or button styling|No|
216
- |Update translation copy|No|
217
- |Change `scrollLock`|No|
218
-
219
- ## Design Guidelines
220
-
221
- * **Start from presets.** Use `policyPackPresets` to get running, then customize for your needs.
222
- * **Keep packs small.** A handful of regional policies is better than dozens of tiny fragments.
223
- * **Think risk, not geography.** Geography is just a matcher — the real question is what consent behavior each region needs.
224
- * **Always include a default.** Unless "no banner for unmatched traffic" is intentional.
225
- * **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.
226
- * **Keep policy IDs stable.** They appear in debugging output, snapshots, and audit records.
227
- * **Use `inspectPolicies()` before deploying.** It catches overlapping matchers, missing defaults, and IAB misconfigurations.
228
-
229
- > ℹ️ **Info:**
230
- > For provider setup, see the Next.js policy pack guide. For backend configuration, see the self-host guide.
@@ -1,41 +0,0 @@
1
- ---
2
- title: useColorScheme
3
- description: Manage light/dark mode preferences for consent components.
4
- group: frameworks
5
- ---
6
- `useColorScheme()` manages the color scheme preference for c15t components. It sets up the appropriate CSS class and media query listeners.
7
-
8
- ```tsx
9
- import { useColorScheme } from '@c15t/nextjs';
10
-
11
- function ThemeManager() {
12
- useColorScheme('system'); // Follow system preference
13
- }
14
- ```
15
-
16
- ## Parameters
17
-
18
- |Value|Behavior|
19
- |--|--|
20
- |`'light'`|Force light mode|
21
- |`'dark'`|Force dark mode|
22
- |`'system'`|Follow `prefers-color-scheme` media query|
23
- |`null`|Disable - c15t won't manage color scheme|
24
- |`undefined`|No-op|
25
-
26
- ## Provider-Level Configuration
27
-
28
- You can also set the color scheme on the provider without using this hook:
29
-
30
- ```tsx
31
- <ConsentManagerProvider
32
- options={{
33
- colorScheme: 'system',
34
- // ...
35
- }}
36
- >
37
- ```
38
-
39
- ## System Preference Detection
40
-
41
- When set to `'system'`, the hook listens for changes to the `prefers-color-scheme` media query and updates automatically when the user changes their OS theme.
@@ -1,96 +0,0 @@
1
- ---
2
- title: Checking Consent
3
- description: Use has() for flexible consent checks with AND, OR, and NOT logic.
4
- Check if any consent exists with hasConsented().
5
- group: frameworks
6
- ---
7
- ## has(condition)
8
-
9
- The `has()` method evaluates whether the current consent state satisfies a condition. It supports simple category checks and complex logical expressions.
10
-
11
- ### Simple Check
12
-
13
- ```tsx
14
- const { has } = useConsentManager();
15
-
16
- if (has('measurement')) {
17
- // User has granted measurement consent
18
- }
19
- ```
20
-
21
- ### AND Logic
22
-
23
- All conditions must be true:
24
-
25
- ```tsx
26
- has({ and: ['measurement', 'marketing'] })
27
- // true only if BOTH measurement AND marketing are granted
28
- ```
29
-
30
- ### OR Logic
31
-
32
- At least one condition must be true:
33
-
34
- ```tsx
35
- has({ or: ['measurement', 'marketing'] })
36
- // true if EITHER measurement OR marketing is granted
37
- ```
38
-
39
- ### NOT Logic
40
-
41
- Negates a condition:
42
-
43
- ```tsx
44
- has({ not: 'marketing' })
45
- // true if marketing consent is NOT granted
46
- ```
47
-
48
- ### Nested Conditions
49
-
50
- Combine operators for complex logic:
51
-
52
- ```tsx
53
- has({
54
- and: [
55
- 'necessary',
56
- { or: ['measurement', 'marketing'] },
57
- { not: 'functionality' },
58
- ],
59
- })
60
- // true if: necessary AND (measurement OR marketing) AND NOT functionality
61
- ```
62
-
63
- ### HasCondition Type
64
-
65
- ```ts
66
- type HasCondition<CategoryType> =
67
- | CategoryType // "measurement"
68
- | { and: HasCondition[] | HasCondition } // { and: ["a", "b"] }
69
- | { or: HasCondition[] | HasCondition } // { or: ["a", "b"] }
70
- | { not: HasCondition } // { not: "a" }
71
- ```
72
-
73
- ## hasConsented()
74
-
75
- Returns `true` if the user has made any consent choice (accepted, rejected, or customized). Returns `false` if no consent has been recorded yet.
76
-
77
- ```tsx
78
- const { hasConsented } = useConsentManager();
79
-
80
- if (hasConsented()) {
81
- // User has previously made a consent choice
82
- } else {
83
- // First visit — no consent recorded
84
- }
85
- ```
86
-
87
- ## getDisplayedConsents()
88
-
89
- Returns the consent types that should be displayed in the UI (based on active `consentCategories` and each type's `display` property):
90
-
91
- ```tsx
92
- const { getDisplayedConsents } = useConsentManager();
93
-
94
- const visibleCategories = getDisplayedConsents();
95
- // Returns ConsentType[] with name, description, defaultValue, etc.
96
- ```
@@ -1,97 +0,0 @@
1
- ---
2
- title: Location Info
3
- description: Access detected jurisdiction, country, and region. Override
4
- geolocation for testing.
5
- group: frameworks
6
- ---
7
- ## locationInfo
8
-
9
- The `locationInfo` state contains the user's detected geographic information:
10
-
11
- ```tsx
12
- const { locationInfo } = useConsentManager();
13
-
14
- if (locationInfo) {
15
- console.log(locationInfo.jurisdiction); // 'GDPR', 'CCPA', etc.
16
- console.log(locationInfo.countryCode); // 'DE', 'US', etc.
17
- console.log(locationInfo.regionCode); // 'BY', 'CA', etc.
18
- }
19
- ```
20
-
21
- `locationInfo` is `null` until the backend responds with geolocation data (or in offline mode if no overrides are set).
22
-
23
- ## Jurisdiction Codes
24
-
25
- |Code|Region|Consent Model|
26
- |--|--|--|
27
- |`GDPR`|European Union|opt-in|
28
- |`UK_GDPR`|United Kingdom|opt-in|
29
- |`CH`|Switzerland|opt-in|
30
- |`BR`|Brazil (LGPD)|opt-in|
31
- |`APPI`|Japan|opt-in|
32
- |`PIPA`|South Korea|opt-in|
33
- |`PIPEDA`|Canada (excl. Quebec)|opt-out|
34
- |`QC_LAW25`|Quebec, Canada|opt-in|
35
- |`CCPA`|California, USA|opt-out|
36
- |`AU`|Australia|opt-out|
37
- |`NONE`|No jurisdiction|null model|
38
-
39
- ## setOverrides()
40
-
41
- Override detected values for testing or manual configuration. This triggers a re-fetch of consent banner data with the new values:
42
-
43
- ```tsx
44
- const { setOverrides } = useConsentManager();
45
-
46
- // Override country (triggers jurisdiction detection)
47
- await setOverrides({ country: 'DE' });
48
-
49
- // Override language
50
- await setOverrides({ language: 'de' });
51
-
52
- // Override both
53
- await setOverrides({ country: 'US', region: 'CA', language: 'es' });
54
- ```
55
-
56
- ## setLocationInfo()
57
-
58
- Directly set location info without triggering a re-fetch:
59
-
60
- ```tsx
61
- const { setLocationInfo } = useConsentManager();
62
-
63
- setLocationInfo({
64
- jurisdiction: 'GDPR',
65
- countryCode: 'DE',
66
- regionCode: 'BY',
67
- });
68
- ```
69
-
70
- ## Testing Different Jurisdictions
71
-
72
- A development-only component for testing consent behavior across jurisdictions:
73
-
74
- ```tsx
75
- function JurisdictionTester() {
76
- const { setOverrides, model, locationInfo } = useConsentManager();
77
-
78
- const testCases = [
79
- { label: 'GDPR', country: 'DE' },
80
- { label: 'CCPA', country: 'US', region: 'CA' },
81
- { label: 'PIPEDA', country: 'CA', region: undefined },
82
- { label: 'QC_LAW25', country: 'CA', region: 'QC' },
83
- { label: 'NONE', country: 'US', region: 'TX' },
84
- ];
85
-
86
- return (
87
- <div>
88
- <p>Current: {locationInfo?.jurisdiction ?? 'none'} → model: {model}</p>
89
- {testCases.map((tc) => (
90
- <button key={tc.label} onClick={() => setOverrides({ country: tc.country, region: tc.region })}>
91
- Test as {tc.label}
92
- </button>
93
- ))}
94
- </div>
95
- );
96
- }
97
- ```
@@ -1,94 +0,0 @@
1
- ---
2
- title: Setting Consent
3
- description: Save, update, and reset consent preferences with setConsent(),
4
- setSelectedConsent(), and saveConsents().
5
- group: frameworks
6
- ---
7
- ## saveConsents(type)
8
-
9
- The primary way to persist consent. Accepts one of three strategies:
10
-
11
- ```tsx
12
- const { saveConsents } = useConsentManager();
13
-
14
- // Accept all - sets every active category to true
15
- await saveConsents('all');
16
-
17
- // Reject all - only necessary stays true, everything else false
18
- await saveConsents('necessary');
19
-
20
- // Save current selections - persists whatever the user toggled
21
- await saveConsents('custom');
22
- ```
23
-
24
- **What happens when you call saveConsents:**
25
-
26
- 1. Consent state is updated in the store
27
- 2. UI closes (activeUI → 'none')
28
- 3. Consent is saved to localStorage and cookie
29
- 4. If consent was revoked and `reloadOnConsentRevoked` is true, the page reloads
30
- 5. Otherwise, scripts/iframes/network blocker are updated
31
- 6. Consent is synced to the backend API
32
-
33
- ## setConsent(name, value)
34
-
35
- Updates a single consent category AND automatically saves it. Use this for simple one-off consent changes:
36
-
37
- ```tsx
38
- const { setConsent } = useConsentManager();
39
-
40
- // Grant measurement consent immediately
41
- setConsent('measurement', true);
42
-
43
- // Revoke marketing consent immediately
44
- setConsent('marketing', false);
45
- ```
46
-
47
- ## setSelectedConsent(name, value)
48
-
49
- Updates the selection state without saving. This is what dialog toggles use - the user can flip toggles without committing until they click "Save":
50
-
51
- ```tsx
52
- const { setSelectedConsent, saveConsents } = useConsentManager();
53
-
54
- // User toggles measurement on
55
- setSelectedConsent('measurement', true);
56
-
57
- // User toggles marketing off
58
- setSelectedConsent('marketing', false);
59
-
60
- // User clicks "Save" - now it persists
61
- await saveConsents('custom');
62
- ```
63
-
64
- ## resetConsents()
65
-
66
- Resets all consent preferences to their default values and clears stored consent:
67
-
68
- ```tsx
69
- const { resetConsents } = useConsentManager();
70
-
71
- resetConsents();
72
- // All consents back to defaults, consent info cleared
73
- ```
74
-
75
- ## Accept All / Reject All Patterns
76
-
77
- Common patterns for banner buttons:
78
-
79
- ```tsx
80
- function ConsentActions() {
81
- const { saveConsents } = useConsentManager();
82
-
83
- return (
84
- <div>
85
- <button onClick={() => saveConsents('necessary')}>
86
- Reject All
87
- </button>
88
- <button onClick={() => saveConsents('all')}>
89
- Accept All
90
- </button>
91
- </div>
92
- );
93
- }
94
- ```
@@ -1,59 +0,0 @@
1
- ---
2
- title: useDraggable
3
- description: Make an element draggable between viewport corners with snapping,
4
- persistence, and animation support.
5
- group: frameworks
6
- ---
7
- `useDraggable()` provides drag-to-corner functionality. Used internally by `ConsentDialogTrigger`, this hook lets you build custom draggable elements that snap to viewport corners.
8
-
9
- ```tsx
10
- import { useDraggable } from '@c15t/nextjs';
11
-
12
- function DraggableButton() {
13
- const { corner, isDragging, handlers, dragStyle } = useDraggable({
14
- defaultPosition: 'bottom-right',
15
- persistPosition: true,
16
- });
17
-
18
- return (
19
- <button
20
- {...handlers}
21
- style={{
22
- ...dragStyle,
23
- position: 'fixed',
24
- // Position based on corner
25
- ...(corner.includes('bottom') ? { bottom: 16 } : { top: 16 }),
26
- ...(corner.includes('right') ? { right: 16 } : { left: 16 }),
27
- }}
28
- >
29
- {isDragging ? 'Dragging...' : 'Drag me'}
30
- </button>
31
- );
32
- }
33
- ```
34
-
35
- ## Options
36
-
37
- |Option|Type|Default|Description|
38
- |--|--|--|--|
39
- |`defaultPosition`|`CornerPosition`|`'bottom-right'`|Initial corner position|
40
- |`persistPosition`|`boolean`|`true`|Save position to localStorage|
41
- |`onPositionChange`|`(position: CornerPosition) => void`|-|Callback on position change|
42
-
43
- ## Return Value
44
-
45
- |Property|Type|Description||||
46
- |--|--|--|--|--|--|
47
- |`corner`|`CornerPosition`|Current corner: `'top-left'`|`'top-right'`|`'bottom-left'`|`'bottom-right'`|
48
- |`isDragging`|`boolean`|Whether the element is being dragged||||
49
- |`isSnapping`|`boolean`|Whether the element is animating to a new corner||||
50
- |`wasDragged`|`() => boolean`|Whether the last interaction was a drag (vs click)||||
51
- |`handlers`|`object`|Pointer event handlers to spread onto the element||||
52
- |`dragStyle`|`CSSProperties`|Transform style for drag offset||||
53
-
54
- ## Behavior
55
-
56
- * Drag starts on pointer down (left click / single touch)
57
- * Movement threshold of 5px distinguishes drag from click
58
- * On pointer up, element snaps to the nearest corner based on drag direction and velocity
59
- * Position persists to localStorage by default
@@ -1,42 +0,0 @@
1
- ---
2
- title: useFocusTrap
3
- description: Trap keyboard focus within a container for accessible modal dialogs.
4
- group: frameworks
5
- ---
6
- `useFocusTrap()` keeps keyboard focus within a container element while active. This is essential for accessibility - when a modal dialog is open, Tab and Shift+Tab should cycle through focusable elements inside the dialog, not escape to the page behind it.
7
-
8
- ```tsx
9
- import { useFocusTrap } from '@c15t/nextjs';
10
- import { useRef } from 'react';
11
-
12
- function AccessibleModal({ isOpen }: { isOpen: boolean }) {
13
- const containerRef = useRef<HTMLDivElement>(null);
14
- useFocusTrap(isOpen, containerRef);
15
-
16
- if (!isOpen) return null;
17
-
18
- return (
19
- <div ref={containerRef} role="dialog" aria-modal="true">
20
- <h2>Modal Title</h2>
21
- <button>Action</button>
22
- <button>Close</button>
23
- </div>
24
- );
25
- }
26
- ```
27
-
28
- ## Parameters
29
-
30
- |Parameter|Type|Description|
31
- |--|--|--|
32
- |`shouldTrap`|`boolean`|Whether focus should be trapped|
33
- |`containerRef`|`RefObject<HTMLElement \|null> \|null`|Ref to the container element|
34
-
35
- ## Behavior
36
-
37
- * **Tab**: Moves focus to the next focusable element. Wraps to the first element when reaching the end.
38
- * **Shift+Tab**: Moves focus to the previous focusable element. Wraps to the last element when reaching the start.
39
- * Focus is restored to the previously focused element when the trap is deactivated.
40
-
41
- > ℹ️ **Info:**
42
- > ConsentBanner and ConsentDialog use useFocusTrap internally when trapFocus is enabled (default: true). You only need this hook when building custom consent UI.
@@ -1,37 +0,0 @@
1
- ---
2
- title: useReducedMotion
3
- description: Detect the user's prefers-reduced-motion OS setting and reactively
4
- disable animations.
5
- group: frameworks
6
- ---
7
- `useReducedMotion()` reads the `prefers-reduced-motion: reduce` media query and reactively updates when the user's preference changes. Use it to conditionally skip animations for users who have enabled reduced motion in their OS accessibility settings.
8
-
9
- The hook returns `false` during SSR to avoid hydration mismatches, then updates to the actual preference on the client.
10
-
11
- ```tsx
12
- import { useReducedMotion } from '@c15t/react/hooks';
13
-
14
- function AnimatedBanner() {
15
- const prefersReducedMotion = useReducedMotion();
16
-
17
- return (
18
- <div
19
- style={{
20
- transition: prefersReducedMotion ? 'none' : 'opacity 300ms ease',
21
- opacity: 1,
22
- }}
23
- >
24
- Consent banner content
25
- </div>
26
- );
27
- }
28
- ```
29
-
30
- ## Return Value
31
-
32
- |Type|Description|
33
- |--|--|
34
- |`boolean`|`true` if the user prefers reduced motion, `false` otherwise|
35
-
36
- > ℹ️ **Info:**
37
- > c15t's built-in components already respect prefers-reduced-motion internally. This hook is primarily useful when building custom UI with the headless approach.