@c15t/nextjs 2.1.0 → 2.2.1

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 (162) hide show
  1. package/AGENTS.md +155 -0
  2. package/README.md +31 -15
  3. package/client/components/integrations.js +3 -0
  4. package/dist/components/integrations/index.cjs +1 -0
  5. package/dist/components/integrations/index.js +1 -0
  6. package/dist/headless.cjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/libs/browser-initial-data.cjs +1 -1
  9. package/dist/libs/initial-data.cjs +1 -1
  10. package/dist/styles.tw3.css +1 -1
  11. package/dist/version.cjs +1 -1
  12. package/dist/version.js +1 -1
  13. package/dist-types/components/integrations/index.d.ts +1 -0
  14. package/dist-types/libs/browser-initial-data.d.ts +1 -1
  15. package/dist-types/version.d.ts +1 -1
  16. package/docs/README.md +155 -73
  17. package/docs/{building-headless-components.md → frameworks/next/building-headless-components.md} +3 -1
  18. package/docs/{callbacks.md → frameworks/next/callbacks.md} +3 -1
  19. package/docs/{components → frameworks/next/components}/consent-banner.md +10 -21
  20. package/docs/{components → frameworks/next/components}/consent-dialog-link.md +8 -5
  21. package/docs/frameworks/next/components/consent-dialog-trigger.md +202 -0
  22. package/docs/{components → frameworks/next/components}/consent-dialog.md +7 -15
  23. package/docs/frameworks/next/components/consent-manager-provider.md +327 -0
  24. package/docs/{components → frameworks/next/components}/consent-widget.md +8 -6
  25. package/docs/{components → frameworks/next/components}/dev-tools.md +9 -6
  26. package/docs/{components → frameworks/next/components}/frame.md +8 -9
  27. package/docs/{concepts → frameworks/next/concepts}/client-modes.md +4 -2
  28. package/docs/{concepts → frameworks/next/concepts}/consent-categories.md +1 -0
  29. package/docs/{concepts → frameworks/next/concepts}/consent-models.md +1 -0
  30. package/docs/{concepts → frameworks/next/concepts}/cookie-management.md +3 -3
  31. package/docs/{concepts → frameworks/next/concepts}/glossary.md +1 -0
  32. package/docs/{concepts → frameworks/next/concepts}/initialization-flow.md +1 -0
  33. package/docs/{concepts → frameworks/next/concepts}/policy-packs.md +1 -0
  34. package/docs/{headless.md → frameworks/next/headless.md} +3 -1
  35. package/docs/{hooks → frameworks/next/hooks}/use-color-scheme.md +1 -0
  36. package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/checking-consent.md +3 -1
  37. package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/location-info.md +3 -1
  38. package/docs/frameworks/next/hooks/use-consent-manager/overview.md +185 -0
  39. package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/setting-consent.md +3 -1
  40. package/docs/{hooks → frameworks/next/hooks}/use-draggable.md +3 -1
  41. package/docs/{hooks → frameworks/next/hooks}/use-focus-trap.md +1 -0
  42. package/docs/{hooks → frameworks/next/hooks}/use-reduced-motion.md +3 -1
  43. package/docs/{hooks → frameworks/next/hooks}/use-ssr-status.md +6 -5
  44. package/docs/{hooks → frameworks/next/hooks}/use-text-direction.md +1 -0
  45. package/docs/frameworks/next/hooks/use-translations.md +55 -0
  46. package/docs/{iab → frameworks/next/iab}/consent-banner.md +9 -12
  47. package/docs/{iab → frameworks/next/iab}/consent-dialog.md +9 -14
  48. package/docs/{iab → frameworks/next/iab}/overview.md +4 -2
  49. package/docs/{iab → frameworks/next/iab}/use-gvl-data.md +3 -2
  50. package/docs/{iframe-blocking.md → frameworks/next/iframe-blocking.md} +8 -9
  51. package/docs/{internationalization.md → frameworks/next/internationalization.md} +13 -3
  52. package/docs/{network-blocker.md → frameworks/next/network-blocker.md} +13 -51
  53. package/docs/{optimization.md → frameworks/next/optimization.md} +5 -4
  54. package/docs/{policy-packs.md → frameworks/next/policy-packs.md} +3 -1
  55. package/docs/{quickstart.md → frameworks/next/quickstart.md} +26 -42
  56. package/docs/{script-loader.md → frameworks/next/script-loader.md} +72 -90
  57. package/docs/{server-side.md → frameworks/next/server-side.md} +12 -15
  58. package/docs/{styling → frameworks/next/styling}/classnames.md +3 -1
  59. package/docs/{styling → frameworks/next/styling}/color-scheme.md +3 -1
  60. package/docs/frameworks/next/styling/css-variables.md +53 -0
  61. package/docs/frameworks/next/styling/overview.md +299 -0
  62. package/docs/frameworks/next/styling/slots.md +94 -0
  63. package/docs/{styling → frameworks/next/styling}/tailwind.md +3 -1
  64. package/docs/frameworks/next/styling/tokens.md +156 -0
  65. package/docs/{troubleshooting.md → frameworks/next/troubleshooting.md} +3 -1
  66. package/docs/integrations/adobe-analytics.md +160 -0
  67. package/docs/integrations/ahrefs-analytics.md +15 -82
  68. package/docs/integrations/amplitude.md +230 -0
  69. package/docs/integrations/building-integrations.md +15 -181
  70. package/docs/integrations/clearbit.md +130 -0
  71. package/docs/integrations/cloudflare-web-analytics.md +15 -83
  72. package/docs/integrations/crisp.md +13 -87
  73. package/docs/integrations/databuddy.md +16 -85
  74. package/docs/integrations/fathom-analytics.md +15 -86
  75. package/docs/integrations/google-maps.md +263 -0
  76. package/docs/integrations/google-tag-manager.md +18 -86
  77. package/docs/integrations/google-tag.md +15 -84
  78. package/docs/integrations/heap.md +219 -0
  79. package/docs/integrations/hightouch.md +199 -0
  80. package/docs/integrations/hotjar.md +15 -83
  81. package/docs/integrations/intercom.md +13 -83
  82. package/docs/integrations/linkedin-insights.md +15 -82
  83. package/docs/integrations/logrocket.md +175 -0
  84. package/docs/integrations/matomo-analytics.md +13 -88
  85. package/docs/integrations/meta-pixel.md +33 -316
  86. package/docs/integrations/microsoft-clarity.md +15 -83
  87. package/docs/integrations/microsoft-uet.md +16 -83
  88. package/docs/integrations/mixpanel-analytics.md +15 -83
  89. package/docs/integrations/overview.md +55 -6
  90. package/docs/integrations/pirsch.md +142 -0
  91. package/docs/integrations/plausible-analytics.md +14 -97
  92. package/docs/integrations/posthog.md +26 -98
  93. package/docs/integrations/promptwatch.md +16 -82
  94. package/docs/integrations/reddit-pixel.md +27 -122
  95. package/docs/integrations/rudderstack.md +271 -0
  96. package/docs/integrations/rybbit-analytics.md +15 -94
  97. package/docs/integrations/segment.md +15 -82
  98. package/docs/integrations/snapchat-pixel.md +21 -102
  99. package/docs/integrations/tiktok-pixel.md +13 -81
  100. package/docs/integrations/umami-analytics.md +16 -88
  101. package/docs/integrations/vercel-analytics.md +13 -85
  102. package/docs/integrations/x-pixel.md +27 -102
  103. package/docs/integrations/youtube.md +193 -0
  104. package/docs/shared/concepts/client-modes.md +103 -0
  105. package/docs/shared/concepts/consent-categories.md +41 -0
  106. package/docs/shared/concepts/consent-models.md +72 -0
  107. package/docs/shared/concepts/cookie-management.md +88 -0
  108. package/docs/shared/concepts/glossary.md +24 -0
  109. package/docs/shared/concepts/initialization-flow.md +105 -0
  110. package/docs/shared/concepts/policy-packs.md +225 -0
  111. package/docs/shared/react/components/consent-banner.md +242 -0
  112. package/docs/shared/react/components/consent-dialog-link.md +45 -0
  113. package/docs/shared/react/components/consent-dialog-trigger.md +185 -0
  114. package/docs/shared/react/components/consent-dialog.md +119 -0
  115. package/docs/shared/react/components/consent-manager-provider.md +225 -0
  116. package/docs/shared/react/components/consent-widget.md +121 -0
  117. package/docs/shared/react/components/dev-tools.md +81 -0
  118. package/docs/shared/react/components/frame.md +52 -0
  119. package/docs/shared/react/guides/building-headless-components.md +110 -0
  120. package/docs/shared/react/guides/callbacks.md +89 -0
  121. package/docs/shared/react/guides/headless.md +31 -0
  122. package/docs/shared/react/guides/iframe-blocking.md +65 -0
  123. package/docs/shared/react/guides/internationalization.md +123 -0
  124. package/docs/shared/react/guides/network-blocker.md +72 -0
  125. package/docs/shared/react/guides/optimization.md +44 -0
  126. package/docs/shared/react/guides/policy-packs.md +173 -0
  127. package/docs/shared/react/guides/script-loader.md +311 -0
  128. package/docs/shared/react/hooks/use-color-scheme.md +31 -0
  129. package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +95 -0
  130. package/docs/shared/react/hooks/use-consent-manager/location-info.md +96 -0
  131. package/docs/shared/react/hooks/use-consent-manager/overview.md +74 -0
  132. package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +93 -0
  133. package/docs/shared/react/hooks/use-draggable.md +30 -0
  134. package/docs/shared/react/hooks/use-focus-trap.md +20 -0
  135. package/docs/shared/react/hooks/use-reduced-motion.md +33 -0
  136. package/docs/shared/react/hooks/use-ssr-status.md +16 -0
  137. package/docs/shared/react/hooks/use-text-direction.md +38 -0
  138. package/docs/shared/react/hooks/use-translations.md +15 -0
  139. package/docs/shared/react/iab/consent-banner.md +60 -0
  140. package/docs/shared/react/iab/consent-dialog.md +76 -0
  141. package/docs/shared/react/iab/overview.md +80 -0
  142. package/docs/shared/react/iab/use-gvl-data.md +21 -0
  143. package/docs/shared/react/styling/classnames.md +93 -0
  144. package/docs/shared/react/styling/color-scheme.md +35 -0
  145. package/docs/shared/react/styling/css-variables.md +53 -0
  146. package/docs/shared/react/styling/overview.md +261 -0
  147. package/docs/shared/react/styling/slots.md +93 -0
  148. package/docs/shared/react/styling/stylesheet-entrypoint.md +8 -0
  149. package/docs/shared/react/styling/tailwind.md +88 -0
  150. package/docs/shared/react/styling/tokens.md +155 -0
  151. package/docs/shared/troubleshooting.md +82 -0
  152. package/package.json +19 -10
  153. package/readme.json +14 -8
  154. package/docs/ai-agents.md +0 -111
  155. package/docs/components/consent-dialog-trigger.md +0 -103
  156. package/docs/components/consent-manager-provider.md +0 -425
  157. package/docs/hooks/use-consent-manager/overview.md +0 -420
  158. package/docs/hooks/use-translations.md +0 -118
  159. package/docs/styling/css-variables.md +0 -92
  160. package/docs/styling/overview.md +0 -457
  161. package/docs/styling/slots.md +0 -128
  162. package/docs/styling/tokens.md +0 -216
@@ -0,0 +1,225 @@
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.
@@ -0,0 +1,242 @@
1
+ ---
2
+ title: Consent Banner
3
+ description: Reference page for consent banner.
4
+ group: reference
5
+ ---
6
+ `ConsentBanner` is a ready-to-use consent banner that appears automatically in **opt-in jurisdictions** (like GDPR) where explicit consent is required before tracking. In opt-out jurisdictions (like CCPA), the banner won't appear — users get an opt-out mechanism instead. It includes reject, accept, and customize buttons with configurable layout.
7
+
8
+ ## Button Layout
9
+
10
+ The `layout` prop controls button arrangement. Each item is either a button ID or an array of button IDs (which groups them together):
11
+
12
+ ```tsx
13
+ {/* Default: reject and accept grouped, customize separate */}
14
+ <ConsentBanner layout={[['reject', 'accept'], 'customize']} />
15
+
16
+ {/* All buttons in one group */}
17
+ <ConsentBanner layout={[['reject', 'customize', 'accept']]} />
18
+
19
+ {/* Accept first, then reject and customize grouped */}
20
+ <ConsentBanner layout={['accept', ['reject', 'customize']]} />
21
+ ```
22
+
23
+ To stack groups vertically, pair the same grouped layout with `direction="column"`:
24
+
25
+ ```tsx
26
+ <ConsentBanner
27
+ layout={['customize', ['reject', 'accept']]}
28
+ direction="column"
29
+ />
30
+ ```
31
+
32
+ ## Policy-Driven UI Profile
33
+
34
+ When using backend runtime policies, `policy.ui.uiProfile` can control banner action presentation:
35
+
36
+ * `compact` — default desktop sizing
37
+ * `balanced` — auto-fills compact and grouped layouts with moderate emphasis
38
+ * `strict` — always fills action controls for explicit, high-clarity layouts
39
+
40
+ ## Theme-Level Button Styling
41
+
42
+ Use the provider `theme` prop to control how stock consent actions look:
43
+
44
+ ```tsx
45
+ <ConsentManagerProvider
46
+ options={{
47
+ theme: {
48
+ consentActions: {
49
+ default: { mode: 'stroke' },
50
+ accept: { variant: 'primary', mode: 'stroke' },
51
+ customize: { variant: 'neutral', mode: 'ghost' },
52
+ },
53
+ },
54
+ }}
55
+ >
56
+ <ConsentBanner />
57
+ </ConsentManagerProvider>
58
+ ```
59
+
60
+ Policy packs control grouping, ordering, and direction. The theme controls button appearance.
61
+
62
+ ## Styling First
63
+
64
+ > ℹ️ **Info:**
65
+ > For pure theming, stay inside the pre-built banner. Start with layout props, theme.consentActions, design tokens, and theme.slots before reaching for compound components. See Styling Overview.
66
+
67
+ The stock banner maps common visual changes to the theme system:
68
+
69
+ * Card background -> `theme.colors.surface`
70
+ * Footer background -> `theme.colors.surfaceHover`
71
+ * Card, footer, and title tweaks -> `theme.slots.consentBannerCard`, `consentBannerFooter`, and `consentBannerTitle`
72
+
73
+ ```tsx
74
+ <ConsentManagerProvider
75
+ options={{
76
+ theme: {
77
+ colors: {
78
+ surface: '#fffdf8',
79
+ surfaceHover: '#f6f3ee',
80
+ },
81
+ slots: {
82
+ consentBannerCard: 'rounded-[28px] shadow-xl',
83
+ consentBannerFooter: 'border-t border-black/10 px-6',
84
+ consentBannerTitle: 'tracking-tight',
85
+ },
86
+ },
87
+ }}
88
+ >
89
+ <ConsentBanner />
90
+ </ConsentManagerProvider>
91
+ ```
92
+
93
+ ### Primary Button
94
+
95
+ Highlight specific button(s) as the primary action:
96
+
97
+ ```tsx
98
+ {/* Single primary */}
99
+ <ConsentBanner primaryButton="accept" />
100
+
101
+ {/* Multiple primaries */}
102
+ <ConsentBanner primaryButton={['accept', 'customize']} />
103
+ ```
104
+
105
+ ## Legal Links
106
+
107
+ Control which legal links appear in the banner description:
108
+
109
+ ```tsx
110
+ {/* Show all configured links (default) */}
111
+ <ConsentBanner legalLinks={undefined} />
112
+
113
+ {/* Show no links */}
114
+ <ConsentBanner legalLinks={null} />
115
+
116
+ {/* Show specific links */}
117
+ <ConsentBanner legalLinks={['privacyPolicy', 'cookiePolicy']} />
118
+ ```
119
+
120
+ > ℹ️ **Info:**
121
+ > Legal link URLs are configured in the ConsentManagerProvider options via the legalLinks prop, not on the banner itself.
122
+
123
+ ## Customizing Copy
124
+
125
+ Prefer provider `i18n` when you want to rename the stock banner content:
126
+
127
+ ```tsx
128
+ <ConsentManagerProvider
129
+ options={{
130
+ i18n: {
131
+ locale: 'en',
132
+ messages: {
133
+ en: {
134
+ cookieBanner: {
135
+ title: 'We value your privacy',
136
+ description: 'We use cookies to improve the site and measure performance.',
137
+ },
138
+ common: {
139
+ acceptAll: 'Accept all',
140
+ rejectAll: 'Reject all',
141
+ customize: 'Manage preferences',
142
+ },
143
+ },
144
+ },
145
+ },
146
+ }}
147
+ >
148
+ <ConsentBanner />
149
+ </ConsentManagerProvider>
150
+ ```
151
+
152
+ Direct text props such as `title`, `description`, and `acceptButtonText` are still supported for one-off overrides, but `i18n` is the preferred path for copy changes.
153
+
154
+ ## Advanced: Compound Components
155
+
156
+ Use compound components only when the stock banner structure is no longer enough and you need to rearrange existing c15t primitives while keeping policy-driven action grouping and emphasis:
157
+
158
+ ```tsx
159
+ <ConsentBanner.Root>
160
+ <ConsentBanner.Overlay />
161
+ <ConsentBanner.Card>
162
+ <ConsentBanner.Header>
163
+ <ConsentBanner.Title />
164
+ <ConsentBanner.Description />
165
+ </ConsentBanner.Header>
166
+ <ConsentBanner.PolicyActions />
167
+ </ConsentBanner.Card>
168
+ </ConsentBanner.Root>
169
+ ```
170
+
171
+ * `ConsentBanner.Root` — Outermost container, provides theme context
172
+ * `ConsentBanner.Card` — Main content card with optional focus trapping
173
+ * `ConsentBanner.Header` — Contains title and description
174
+ * `ConsentBanner.Title` — Heading, defaults to translation `consentBanner.title`
175
+ * `ConsentBanner.Description` — Description text, supports `legalLinks` prop
176
+ * `ConsentBanner.PolicyActions` — Renders policy-aware grouped actions inside the banner footer
177
+ * `ConsentBanner.Footer` — Action buttons container
178
+ * `ConsentBanner.FooterSubGroup` — Groups related buttons together
179
+ * `ConsentBanner.RejectButton` — Rejects all consent
180
+ * `ConsentBanner.CustomizeButton` — Opens the consent dialog
181
+ * `ConsentBanner.AcceptButton` — Accepts all consent
182
+ * `ConsentBanner.Overlay` — Optional backdrop overlay
183
+
184
+ For a fixed layout that intentionally ignores policy grouping, render the footer manually:
185
+
186
+ ```tsx
187
+ <ConsentBanner.Root>
188
+ <ConsentBanner.Card>
189
+ <ConsentBanner.Header>
190
+ <ConsentBanner.Title />
191
+ <ConsentBanner.Description />
192
+ </ConsentBanner.Header>
193
+ <ConsentBanner.Footer>
194
+ <ConsentBanner.FooterSubGroup>
195
+ <ConsentBanner.RejectButton />
196
+ <ConsentBanner.AcceptButton />
197
+ </ConsentBanner.FooterSubGroup>
198
+ <ConsentBanner.CustomizeButton />
199
+ </ConsentBanner.Footer>
200
+ </ConsentBanner.Card>
201
+ </ConsentBanner.Root>
202
+ ```
203
+
204
+ ## Using `renderAction` with c15t Defaults
205
+
206
+ `ConsentBanner.PolicyActions` renders stock c15t buttons and translations by default.
207
+
208
+ ```tsx
209
+ <ConsentBanner.PolicyActions />
210
+ ```
211
+
212
+ `renderAction` is optional. When you want custom mapping but still want the built-in c15t button behavior and copy, return the stock button compounds:
213
+
214
+ ```tsx
215
+ <ConsentBanner.PolicyActions
216
+ renderAction={(action, props) => {
217
+ const { key, ...buttonProps } = props
218
+
219
+ switch (action) {
220
+ case 'accept':
221
+ return <ConsentBanner.AcceptButton key={key} {...buttonProps} />
222
+ case 'reject':
223
+ return <ConsentBanner.RejectButton key={key} {...buttonProps} />
224
+ case 'customize':
225
+ return <ConsentBanner.CustomizeButton key={key} {...buttonProps} />
226
+ }
227
+ }}
228
+ />
229
+ ```
230
+
231
+ `renderAction` is still meant for stock button compounds. If you want completely custom button elements and click handling, use `useHeadlessConsentUI()` and render `banner.actionGroups` manually instead of `ConsentBanner.PolicyActions`.
232
+
233
+ If you only need styling changes, stay with tokens and slots instead of rebuilding the banner layout.
234
+
235
+ ## Props
236
+
237
+ |Property|Value|
238
+ |:--|:--|
239
+ |Type Name|\`ConsentBannerProps\`|
240
+ |Source Path|\`./packages/react/src/components/consent-banner/consent-banner.tsx\`|
241
+
242
+ \*ExtractedTypeTable: Could not extract "ConsentBannerProps" from "./packages/react/src/components/consent-banner/consent-banner.tsx" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: Consent Dialog Link
3
+ description: Reference page for consent dialog link.
4
+ group: reference
5
+ ---
6
+ `ConsentDialogLink` is an inline trigger for opening the consent dialog from places like site footers, legal pages, or account settings. It is unstyled by default, so it inherits your app's typography and link/button styles.
7
+
8
+ ## Footer Link (unstyled by default)
9
+
10
+ ```tsx
11
+ <footer>
12
+ <ConsentDialogLink>
13
+ Your privacy settings
14
+ </ConsentDialogLink>
15
+ </footer>
16
+ ```
17
+
18
+ ## Render as an Anchor
19
+
20
+ Use `asChild` to keep semantic anchor markup while still opening the dialog:
21
+
22
+ ```tsx
23
+ <ConsentDialogLink asChild>
24
+ <a href="#privacy-settings">Manage Preferences</a>
25
+ </ConsentDialogLink>
26
+ ```
27
+
28
+ ## Optional Styling Control
29
+
30
+ The component defaults to `noStyle={true}`. Set it to `false` if you want c15t button styles:
31
+
32
+ ```tsx
33
+ <ConsentDialogLink noStyle={false}>
34
+ Privacy Settings
35
+ </ConsentDialogLink>
36
+ ```
37
+
38
+ ## Props
39
+
40
+ |Property|Value|
41
+ |:--|:--|
42
+ |Type Name|\`ConsentDialogLinkProps\`|
43
+ |Source Path|\`./packages/react/src/components/consent-preferences-link/consent-preferences-link.tsx\`|
44
+
45
+ \*ExtractedTypeTable: Could not extract "ConsentDialogLinkProps" from "./packages/react/src/components/consent-preferences-link/consent-preferences-link.tsx" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
@@ -0,0 +1,185 @@
1
+ ---
2
+ title: Consent Dialog Trigger
3
+ description: Reference page for consent dialog trigger.
4
+ group: reference
5
+ ---
6
+ `ConsentDialogTrigger` is a floating button that opens the consent dialog when clicked. Users can drag it to any corner of the screen, and the position persists across sessions. Use it to give users a persistent way to manage their privacy settings.
7
+
8
+ ## Icon Options
9
+
10
+ ```tsx
11
+ {/* Built-in icons */}
12
+ <ConsentDialogTrigger icon="branding" /> {/* c15t logo (default) */}
13
+ <ConsentDialogTrigger icon="fingerprint" /> {/* Privacy icon */}
14
+ <ConsentDialogTrigger icon="settings" /> {/* Gear icon */}
15
+
16
+ {/* Custom icon */}
17
+ <ConsentDialogTrigger icon={<MyCustomIcon />} />
18
+ ```
19
+
20
+ ## Configurable Toolbar
21
+
22
+ Use the separate `ConsentDialogTriggerToolbar` when you want to place app-owned controls beside the privacy trigger. It always renders exactly one built-in action that opens consent preferences, so `actions` only contains controls your app owns.
23
+
24
+ ```tsx
25
+ <ConsentDialogTriggerToolbar
26
+ ariaLabel="Site controls"
27
+ actions={[
28
+ {
29
+ id: 'theme',
30
+ label: isDark ? 'Switch to light theme' : 'Switch to dark theme',
31
+ icon: isDark ? <SunIcon /> : <MoonIcon />,
32
+ pressed: isDark,
33
+ onSelect: toggleColorScheme,
34
+ },
35
+ {
36
+ id: 'support',
37
+ label: 'Open support chat',
38
+ icon: <ChatIcon />,
39
+ onSelect: openSupportChat,
40
+ },
41
+ ]}
42
+ preferences={{
43
+ icon: 'fingerprint',
44
+ label: 'Manage privacy settings',
45
+ }}
46
+ />
47
+ ```
48
+
49
+ Each custom action requires a stable `id`, accessible `label`, `icon`, and `onSelect` callback. Use `pressed` for toggle actions and `disabled` for unavailable actions. Your app remains responsible for state, integrations, and changing the icon or label. The preferences action automatically moves to the edge nearest the toolbar's snapped corner while custom actions keep their configured order.
50
+
51
+ Toolbars are horizontal by default. Set `orientation="vertical"` to stack the actions and enable Up/Down arrow-key navigation:
52
+
53
+ ```tsx
54
+ <ConsentDialogTriggerToolbar
55
+ orientation="vertical"
56
+ actions={toolbarActions}
57
+ />
58
+ ```
59
+
60
+ ### Toolbar Styling
61
+
62
+ The toolbar follows the standard styling precedence: internal CSS module styles, provider theme slots, then direct component overrides. Use `className` and `style` on the toolbar, or on an individual action, and use `noStyle` for a fully custom implementation.
63
+
64
+ ```tsx
65
+ <ConsentManagerProvider
66
+ options={{
67
+ theme: {
68
+ slots: {
69
+ consentDialogTriggerToolbar: 'my-toolbar',
70
+ consentDialogTriggerToolbarItem: 'my-toolbar-item',
71
+ consentDialogTriggerToolbarIcon: 'my-toolbar-icon',
72
+ },
73
+ },
74
+ }}
75
+ >
76
+ <ConsentDialogTriggerToolbar
77
+ className="fixed-toolbar"
78
+ style={{ '--cdtt-offset': '24px' }}
79
+ actions={[
80
+ {
81
+ id: 'support',
82
+ label: 'Open support chat',
83
+ icon: <ChatIcon />,
84
+ onSelect: openSupportChat,
85
+ className: 'support-action',
86
+ },
87
+ ]}
88
+ />
89
+ </ConsentManagerProvider>
90
+ ```
91
+
92
+ Available toolbar theme slots are `consentDialogTriggerToolbar`, `consentDialogTriggerToolbarItem`, and `consentDialogTriggerToolbarIcon`.
93
+
94
+ With `noStyle`, use `data-corner`, `data-dragging`, and `data-snapping` on the toolbar to style its current position and interaction state.
95
+
96
+ ## Visibility
97
+
98
+ Control when the trigger is visible:
99
+
100
+ ```tsx
101
+ {/* Always visible (default) */}
102
+ <ConsentDialogTrigger showWhen="always" />
103
+
104
+ {/* Only after user has made a consent choice */}
105
+ <ConsentDialogTrigger showWhen="after-consent" />
106
+
107
+ {/* Hidden (control visibility programmatically) */}
108
+ <ConsentDialogTrigger showWhen="never" />
109
+ ```
110
+
111
+ ## Position
112
+
113
+ Set the default corner and control persistence:
114
+
115
+ ```tsx
116
+ <ConsentDialogTrigger
117
+ defaultPosition="bottom-left"
118
+ persistPosition={true} // Remembers user's drag position
119
+ onPositionChange={(position) => console.log('Moved to:', position)}
120
+ />
121
+ ```
122
+
123
+ ## Size
124
+
125
+ ```tsx
126
+ <ConsentDialogTrigger size="sm" /> {/* Small */}
127
+ <ConsentDialogTrigger size="md" /> {/* Medium (default) */}
128
+ <ConsentDialogTrigger size="lg" /> {/* Large */}
129
+ ```
130
+
131
+ ## Compound Components
132
+
133
+ Build fully custom trigger layouts using sub-components:
134
+
135
+ ```tsx
136
+ <ConsentDialogTrigger.Root defaultPosition="bottom-right">
137
+ <ConsentDialogTrigger.Button size="md">
138
+ <ConsentDialogTrigger.Icon icon="settings" />
139
+ <ConsentDialogTrigger.Text>Privacy</ConsentDialogTrigger.Text>
140
+ </ConsentDialogTrigger.Button>
141
+ </ConsentDialogTrigger.Root>
142
+ ```
143
+
144
+ * `ConsentDialogTrigger.Root` — Portal wrapper with drag handling and position persistence
145
+ * `ConsentDialogTrigger.Button` — Draggable button element with size variants
146
+ * `ConsentDialogTrigger.Icon` — Icon display (branding, fingerprint, settings, or custom)
147
+ * `ConsentDialogTrigger.Text` — Optional text label
148
+
149
+ ## Props
150
+
151
+ ### ConsentDialogTrigger
152
+
153
+ |Property|Value|
154
+ |:--|:--|
155
+ |Type Name|\`ConsentDialogTriggerProps\`|
156
+ |Source Path|\`./packages/react/src/components/consent-dialog-trigger/types.ts\`|
157
+
158
+ \*ExtractedTypeTable: Could not extract "ConsentDialogTriggerProps" from "./packages/react/src/components/consent-dialog-trigger/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
159
+
160
+ ### ConsentDialogTriggerToolbar
161
+
162
+ |Property|Value|
163
+ |:--|:--|
164
+ |Type Name|\`ConsentDialogTriggerToolbarProps\`|
165
+ |Source Path|\`./packages/react/src/components/consent-dialog-trigger/types.ts\`|
166
+
167
+ \*ExtractedTypeTable: Could not extract "ConsentDialogTriggerToolbarProps" from "./packages/react/src/components/consent-dialog-trigger/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
168
+
169
+ ### Toolbar Actions
170
+
171
+ |Property|Value|
172
+ |:--|:--|
173
+ |Type Name|\`ConsentDialogTriggerToolbarAction\`|
174
+ |Source Path|\`./packages/react/src/components/consent-dialog-trigger/types.ts\`|
175
+
176
+ \*ExtractedTypeTable: Could not extract "ConsentDialogTriggerToolbarAction" from "./packages/react/src/components/consent-dialog-trigger/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
177
+
178
+ ### Preferences Action
179
+
180
+ |Property|Value|
181
+ |:--|:--|
182
+ |Type Name|\`ConsentDialogTriggerToolbarPreferences\`|
183
+ |Source Path|\`./packages/react/src/components/consent-dialog-trigger/types.ts\`|
184
+
185
+ \*ExtractedTypeTable: Could not extract "ConsentDialogTriggerToolbarPreferences" from "./packages/react/src/components/consent-dialog-trigger/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*