@c15t/react 3.0.0-alpha.3 → 3.0.0-alpha.5

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 (219) hide show
  1. package/AGENTS.md +92 -72
  2. package/README.md +21 -17
  3. package/SKILL.md +34 -0
  4. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  5. package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
  6. package/dist/components/consent-gate/atoms.js +1 -1
  7. package/dist/components/consent-gate/consent-gate.js +1 -1
  8. package/dist/components/iab-panel/atoms/card.js +1 -1
  9. package/dist/components/iab-panel/atoms/feature-item.js +2 -0
  10. package/dist/components/iab-panel/atoms/header.js +1 -1
  11. package/dist/components/iab-panel/iab-panel.js +1 -1
  12. package/dist/components/iab-panel/index.js +1 -1
  13. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  14. package/dist/components/iab-prompt/atoms/root.js +1 -1
  15. package/dist/components/panel/atoms/card.js +1 -1
  16. package/dist/components/panel/atoms/root.js +1 -1
  17. package/dist/components/panel-trigger/atoms/button.js +1 -1
  18. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  19. package/dist/components/preferences/atoms/accordion.js +1 -1
  20. package/dist/components/prompt/atoms/overlay.js +1 -1
  21. package/dist/components/prompt/atoms/root.js +1 -1
  22. package/dist/components/prompt/components.js +1 -1
  23. package/dist/components/shared/libs/compose-refs.js +1 -0
  24. package/dist/components/shared/libs/slot.js +1 -1
  25. package/dist/components/shared/primitives/button.js +1 -1
  26. package/dist/components/shared/ui/dialog/dialog.js +1 -1
  27. package/dist/components/shared/ui/icon.js +1 -1
  28. package/dist/context/consent-tracking-context.js +1 -1
  29. package/dist/devtools.js +1 -1
  30. package/dist/draft-context.js +2 -0
  31. package/dist/draft.js +1 -1
  32. package/dist/external-iab-context.js +1 -1
  33. package/dist/gpp.js +2 -0
  34. package/dist/hooks/use-color-scheme.js +1 -1
  35. package/dist/hooks/use-focus-trap.js +1 -1
  36. package/dist/hooks.js +1 -1
  37. package/dist/iab/styles.tw3.css +4 -4
  38. package/dist/index-parts/consent-banner.js +1 -0
  39. package/dist/index-parts/consent-dialog-link.js +1 -0
  40. package/dist/index-parts/consent-dialog-trigger.js +1 -0
  41. package/dist/index-parts/consent-dialog.js +1 -0
  42. package/dist/index-parts/consent-gate.js +1 -0
  43. package/dist/index-parts/core.js +1 -0
  44. package/dist/index-parts/draft.js +1 -0
  45. package/dist/index-parts/hooks.js +1 -0
  46. package/dist/index-parts/module-hooks.js +1 -0
  47. package/dist/index-parts/provider.js +1 -0
  48. package/dist/index-parts/theme.js +1 -0
  49. package/dist/index-parts/translations.js +1 -0
  50. package/dist/index.js +1 -1
  51. package/dist/kernel-selector.js +1 -1
  52. package/dist/module-hooks/iframe-blocker.js +1 -1
  53. package/dist/postcss-tailwind3.js +1 -0
  54. package/dist/provider.js +1 -1
  55. package/dist/server/fetch-ssr-data.js +1 -1
  56. package/dist/server/headers.js +1 -1
  57. package/dist/server/normalize-url.js +1 -1
  58. package/dist/styles.tw3.css +16 -16
  59. package/dist/transports/offline.js +1 -1
  60. package/dist/utils/merge-slot-props.js +1 -1
  61. package/dist/utils/theme-utils.js +1 -1
  62. package/dist/version.js +1 -1
  63. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
  64. package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
  65. package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
  66. package/dist-types/components/iab-panel/index.d.ts +5 -2
  67. package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
  68. package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
  69. package/dist-types/components/iab-prompt/index.d.ts +2 -2
  70. package/dist-types/components/panel/atoms/card.d.ts +1 -0
  71. package/dist-types/components/prompt/components.d.ts +6 -0
  72. package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
  73. package/dist-types/components/shared/ui/icon.d.ts +2 -1
  74. package/dist-types/context/consent-tracking-context.d.ts +10 -0
  75. package/dist-types/context/theme-context.d.ts +2 -2
  76. package/dist-types/context.d.ts +6 -0
  77. package/dist-types/devtools.d.ts +13 -2
  78. package/dist-types/draft-context.d.ts +31 -0
  79. package/dist-types/draft.d.ts +13 -30
  80. package/dist-types/gpp.d.ts +27 -0
  81. package/dist-types/hooks/use-focus-trap.d.ts +3 -1
  82. package/dist-types/hooks.d.ts +57 -13
  83. package/dist-types/iab.d.ts +1 -1
  84. package/dist-types/index-parts/consent-banner.d.ts +7 -0
  85. package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
  86. package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
  87. package/dist-types/index-parts/consent-dialog.d.ts +7 -0
  88. package/dist-types/index-parts/consent-gate.d.ts +7 -0
  89. package/dist-types/index-parts/core.d.ts +8 -0
  90. package/dist-types/index-parts/draft.d.ts +7 -0
  91. package/dist-types/index-parts/hooks.d.ts +7 -0
  92. package/dist-types/index-parts/module-hooks.d.ts +7 -0
  93. package/dist-types/index-parts/provider.d.ts +7 -0
  94. package/dist-types/index-parts/theme.d.ts +8 -0
  95. package/dist-types/index-parts/translations.d.ts +7 -0
  96. package/dist-types/index.d.ts +14 -15
  97. package/dist-types/kernel-selector.d.ts +0 -8
  98. package/dist-types/module-hooks/iframe-blocker.d.ts +12 -7
  99. package/dist-types/postcss-tailwind3.d.ts +21 -0
  100. package/dist-types/provider.d.ts +57 -17
  101. package/dist-types/server/headers.d.ts +14 -4
  102. package/dist-types/server/normalize-url.d.ts +14 -7
  103. package/dist-types/server/types.d.ts +12 -1
  104. package/dist-types/transports/offline.d.ts +7 -16
  105. package/dist-types/ui-config-context.d.ts +7 -1
  106. package/dist-types/utils/merge-slot-props.d.ts +15 -1
  107. package/dist-types/utils/theme-utils.d.ts +17 -2
  108. package/dist-types/version.d.ts +1 -1
  109. package/docs/README.md +92 -72
  110. package/docs/assets/v3/bottom-bar.png +0 -0
  111. package/docs/assets/v3/brand-card.png +0 -0
  112. package/docs/assets/v3/brand-preferences.png +0 -0
  113. package/docs/assets/v3/choice-wall.png +0 -0
  114. package/docs/assets/v3/headless-bar-html.png +0 -0
  115. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  116. package/docs/assets/v3/headless-bar.png +0 -0
  117. package/docs/assets/v3/slim-bar.png +0 -0
  118. package/docs/concepts/choose-your-setup.md +87 -0
  119. package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
  120. package/docs/{guides → concepts}/consent-state.md +89 -105
  121. package/docs/{guides → concepts}/data-fetching.md +31 -27
  122. package/docs/concepts/how-consent-works.md +123 -0
  123. package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
  124. package/docs/customization/class-names.md +202 -0
  125. package/docs/customization/dark-mode.md +157 -0
  126. package/docs/customization/motion.md +119 -0
  127. package/docs/customization/overview.md +67 -34
  128. package/docs/customization/recipes.md +839 -49
  129. package/docs/customization/slots.md +216 -35
  130. package/docs/customization/stylesheets.md +147 -0
  131. package/docs/customization/tailwind.md +842 -0
  132. package/docs/customization/tokens.md +163 -96
  133. package/docs/customization/translations.md +60 -3
  134. package/docs/frameworks/react/callbacks.md +118 -0
  135. package/docs/frameworks/react/clear-on-revocation.md +106 -0
  136. package/docs/frameworks/react/components/consent-banner.md +42 -28
  137. package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
  138. package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
  139. package/docs/frameworks/react/components/consent-dialog.md +56 -50
  140. package/docs/frameworks/react/components/consent-gate.md +37 -20
  141. package/docs/frameworks/react/components/consent-provider.md +130 -0
  142. package/docs/frameworks/react/components/consent-widget.md +23 -20
  143. package/docs/frameworks/react/components/dev-tools.md +53 -85
  144. package/docs/frameworks/react/components.md +105 -0
  145. package/docs/frameworks/react/compose.md +354 -0
  146. package/docs/frameworks/react/content-security-policy.md +126 -0
  147. package/docs/frameworks/react/customize.md +250 -0
  148. package/docs/frameworks/react/embeds.md +89 -0
  149. package/docs/frameworks/react/gpp.md +163 -0
  150. package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
  151. package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
  152. package/docs/frameworks/react/network-blocker.md +35 -41
  153. package/docs/frameworks/react/quickstart.md +139 -101
  154. package/docs/frameworks/react/rendering.md +128 -0
  155. package/docs/frameworks/react/scripts.md +115 -0
  156. package/docs/frameworks/react/translations.md +155 -0
  157. package/docs/frameworks/react/troubleshooting.md +92 -47
  158. package/docs/frameworks/react/vendor-consent.md +170 -0
  159. package/docs/guides/banner-experiments.md +654 -0
  160. package/docs/guides/troubleshooting.md +120 -47
  161. package/docs/guides/verify-consent.md +81 -49
  162. package/docs/integrations/adobe-analytics.md +167 -159
  163. package/docs/integrations/ahrefs-analytics.md +152 -154
  164. package/docs/integrations/amplitude.md +162 -156
  165. package/docs/integrations/building-integrations.md +136 -37
  166. package/docs/integrations/clearbit.md +154 -154
  167. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  168. package/docs/integrations/cloudflare-zaraz.md +209 -261
  169. package/docs/integrations/crisp.md +164 -158
  170. package/docs/integrations/databuddy.md +157 -173
  171. package/docs/integrations/fathom-analytics.md +158 -156
  172. package/docs/integrations/front-chat.md +167 -167
  173. package/docs/integrations/google-maps.md +118 -83
  174. package/docs/integrations/google-tag-manager.md +178 -163
  175. package/docs/integrations/google-tag.md +163 -160
  176. package/docs/integrations/heap.md +163 -155
  177. package/docs/integrations/hightouch.md +161 -157
  178. package/docs/integrations/hotjar.md +159 -155
  179. package/docs/integrations/intercom.md +183 -153
  180. package/docs/integrations/klaviyo.md +486 -0
  181. package/docs/integrations/linkedin-insights.md +174 -150
  182. package/docs/integrations/logrocket.md +160 -156
  183. package/docs/integrations/matomo-analytics.md +188 -178
  184. package/docs/integrations/meta-pixel.md +188 -150
  185. package/docs/integrations/microsoft-clarity.md +163 -155
  186. package/docs/integrations/microsoft-uet.md +148 -154
  187. package/docs/integrations/mixpanel-analytics.md +155 -160
  188. package/docs/integrations/one-dollar-stats.md +166 -165
  189. package/docs/integrations/openai-pixel.md +204 -301
  190. package/docs/integrations/overview.md +137 -80
  191. package/docs/integrations/pinterest-tag.md +191 -183
  192. package/docs/integrations/pirsch.md +169 -159
  193. package/docs/integrations/plausible-analytics.md +172 -158
  194. package/docs/integrations/posthog.md +313 -240
  195. package/docs/integrations/promptwatch.md +154 -154
  196. package/docs/integrations/reddit-pixel.md +185 -157
  197. package/docs/integrations/rudderstack.md +201 -186
  198. package/docs/integrations/rybbit-analytics.md +171 -160
  199. package/docs/integrations/segment.md +182 -154
  200. package/docs/integrations/snapchat-pixel.md +186 -156
  201. package/docs/integrations/tiktok-pixel.md +171 -150
  202. package/docs/integrations/umami-analytics.md +163 -158
  203. package/docs/integrations/vercel-analytics.md +166 -157
  204. package/docs/integrations/x-pixel.md +176 -150
  205. package/docs/integrations/youtube.md +121 -86
  206. package/docs/upgrade-v3.md +496 -467
  207. package/package.json +20 -9
  208. package/dist/ui-save.js +0 -1
  209. package/dist-types/ui-save.d.ts +0 -25
  210. package/docs/assets/v3/brand-bar.png +0 -0
  211. package/docs/assets/v3/mobile-card.png +0 -0
  212. package/docs/assets/v3/preferences.png +0 -0
  213. package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
  214. package/docs/frameworks/react/script-loader.md +0 -69
  215. package/docs/frameworks/react/styling/overview.md +0 -266
  216. package/docs/guides/deployment-modes.md +0 -75
  217. package/docs/guides/shared-consent-controls.md +0 -158
  218. package/docs/integrations/clear-on-revocation.md +0 -167
  219. package/docs/integrations/granular-consent.md +0 -210
@@ -0,0 +1,250 @@
1
+ ---
2
+ title: Customize
3
+ description: Change the colors, fonts, layout, button styles and copy of the
4
+ c15t banner and dialog in a React app with the stylesheet, theme tokens,
5
+ provider options and slots.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Pick the right tool
10
+
11
+ | Change | Use |
12
+ | ------------------------------------ | ------------------------------------------------------------------- |
13
+ | Brand colors, fonts, radius, spacing | [Theme tokens](#change-colors-fonts-and-radius) |
14
+ | Light and dark colors, animation | [Dark mode and motion](#turn-on-dark-mode-and-change-motion) |
15
+ | Which button is filled or outlined | [`consentActions`](#style-the-action-buttons) |
16
+ | Banner shape and position | [`ConsentBanner` props or `presentation`](#change-the-banner-shape) |
17
+ | One element of a component | [Component parts](#style-one-component-part) |
18
+ | Labels and languages | [Copy and translations](#change-the-copy) |
19
+ | Different markup entirely | [Headless](./headless.md) |
20
+
21
+ [Customization](../../customization/overview.md) explains how these choices relate
22
+ across frameworks.
23
+
24
+ See the [design gallery](../../customization/recipes.md) for five banner designs,
25
+ from a bottom bar to a fully custom one, with tested code for this framework.
26
+
27
+ ## Import the stylesheet
28
+
29
+ Import the stylesheet once, in the module that renders `ConsentProvider`. The
30
+ [quickstart](./quickstart.md) does this in `src/consent.tsx`:
31
+
32
+ ```tsx
33
+ import 'c15t/react/styles.css';
34
+ ```
35
+
36
+ The import is the same with or without Tailwind. To import the stylesheet from
37
+ CSS instead, put it at the top of your global stylesheet, above any
38
+ `@tailwind` directives. `postcss-import`, which Vite uses, ignores an
39
+ `@import` that follows other rules. Tailwind 3 also needs a PostCSS plugin;
40
+ [Tailwind CSS](../../customization/tailwind.md#set-up-tailwind-css-3) shows the
41
+ setup.
42
+
43
+ ## Change colors, fonts and radius
44
+
45
+ Colors, radii, shadows, typography, and motion come from `--c15t-*` custom
46
+ properties. The package stylesheet sets the default values. To change them,
47
+ render `ConsentTheme` with your theme where your app renders on the server,
48
+ or override the variables directly in your CSS.
49
+
50
+ `ConsentTheme` renders a `<style id="c15t-theme">` element with the
51
+ variables for `theme`. It is not a client component: rendered from a Server
52
+ Component or another server-only file, the theme generator stays out of the
53
+ browser bundle. Rendered from a client component it still works, but the
54
+ browser downloads the generator (about 1.7 KB gzip). The provider no longer
55
+ turns `theme` tokens into CSS. It still reads `consentActions` and slot
56
+ styles from its `theme` option, and warns in development when `theme` holds
57
+ tokens but the page has no `c15t-theme` stylesheet.
58
+
59
+ | `ConsentTheme` prop | Purpose |
60
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
61
+ | `theme` | Colors, `dark` colors, typography, spacing, radius, shadows and motion. Defaults to the built-in theme. |
62
+ | `colorScheme` | `'dark'` makes the dark tokens the default before hydration. `'system'` follows `prefers-color-scheme` with a CSS media query. Omit it to follow a `dark` or `c15t-dark` class on `<html>`. |
63
+ | `nonce` | Content Security Policy nonce for the `<style>` element. |
64
+
65
+ Pass the same `colorScheme` to `ConsentTheme` and to the provider. If your
66
+ app manages dark mode through a root class, such as next-themes' `dark`
67
+ class, omit `colorScheme` and include that class in the server HTML.
68
+ [Dark mode](../../customization/dark-mode.md) covers `theme.dark`, `null` and
69
+ a dark first paint.
70
+
71
+ To switch between different token sets at runtime, render `ConsentTheme`
72
+ from a client component and change its props, which ships the generator.
73
+
74
+ Outside a component, call `generateThemeCSS(theme, colorScheme)` from
75
+ `c15t/react/utils`, or `@c15t/react/utils` with the scoped package. It
76
+ writes the same CSS as `ConsentTheme`. Outside React, import it from
77
+ `@c15t/ui/theme`. Call it on the server or at build time and put the result in a
78
+ `<style>` element or your stylesheet. It escapes `<`, so the result is safe
79
+ inside `<style>`.
80
+
81
+ A single-page app has no server render, so pick one of two ways:
82
+
83
+ * **Set the variables in your CSS.** Nothing extra ships to the browser. Load
84
+ this after `c15t/react/styles.css`:
85
+
86
+ ```css title="src/index.css"
87
+ :root {
88
+ --c15t-primary: #2f6f4e;
89
+ --c15t-primary-hover: #24563c;
90
+ --c15t-text-on-primary: #fff;
91
+ --c15t-radius-lg: 4px;
92
+ }
93
+ ```
94
+
95
+ * **Render `ConsentTheme` next to the provider.** Use this when the theme lives
96
+ in TypeScript or changes at runtime. The browser downloads the theme
97
+ generator.
98
+
99
+ ```tsx title="src/consent-theme.ts"
100
+ import { defineTheme } from 'c15t/react';
101
+
102
+ export const theme = defineTheme({
103
+ colors: { primary: '#2f6f4e', primaryHover: '#24563c' },
104
+ radius: { lg: '4px' },
105
+ });
106
+ ```
107
+
108
+ In `src/consent.tsx`, render `<ConsentTheme theme={theme} />` from
109
+ `c15t/react` just before `<ConsentProvider>`.
110
+
111
+ In a server-rendered app, such as React Router framework mode, render
112
+ `ConsentTheme` in the root component so the `<style>` element is part of the
113
+ server HTML. [Theme tokens](../../customization/tokens.md) lists every variable.
114
+
115
+ `ConsentTheme` outranks a plain `:root` rule, wherever each one loads. If you
116
+ use both, a variable set in both places takes the `ConsentTheme` value. Write
117
+ your CSS overrides on `:root:root` to beat it, or move the values into the
118
+ theme.
119
+
120
+ ## Style the action buttons
121
+
122
+ `consentActions` decides how each action looks. `default` applies to every
123
+ button, `primary` to the action the policy or your props mark primary, and
124
+ `accept`, `reject`, `customize` and `dismiss` to one action each. Pass it
125
+ through the provider's `theme` option:
126
+
127
+ ```tsx title="src/consent.tsx"
128
+ <ConsentProvider
129
+ options={{
130
+ mode,
131
+ scripts,
132
+ theme: {
133
+ consentActions: {
134
+ primary: { variant: 'primary', mode: 'filled' },
135
+ dismiss: { variant: 'neutral', mode: 'stroke' },
136
+ },
137
+ },
138
+ }}
139
+ >
140
+ ```
141
+
142
+ The provider's `theme` option only reads `consentActions` and slot styles.
143
+ Tokens passed there do nothing, and the provider logs a warning in development.
144
+ Changing button styles does not change the policy or expire a saved choice.
145
+
146
+ ## Change the banner shape
147
+
148
+ `ConsentBanner` takes `variant` (`floating`, `bar`, `widget` or `wall`) and
149
+ `position`. To use one shape everywhere, set `presentation.prompt` in the
150
+ provider options instead. This banner is a full-width bar for notices and a
151
+ card for choices:
152
+
153
+ ```tsx title="src/regional-banner.tsx"
154
+ import { ConsentBanner, usePolicyRule } from 'c15t/react';
155
+
156
+ export function RegionalBanner() {
157
+ const policy = usePolicyRule();
158
+ return (
159
+ <ConsentBanner variant={policy?.prompt === 'notice' ? 'bar' : 'floating'} />
160
+ );
161
+ }
162
+ ```
163
+
164
+ Render `RegionalBanner` in place of `ConsentBanner` inside the provider.
165
+ [ConsentBanner](./components/consent-banner.md#variants)
166
+ lists every variant and position, and how to order buttons per policy.
167
+
168
+ ## Style one component part
169
+
170
+ Each component part is a slot. Set slots in the provider options under
171
+ `components`, keyed by component and part. A slot takes any attribute the
172
+ element accepts, such as `className` or `style`.
173
+
174
+ The root also carries `data-variant`, so you can restyle one shape without
175
+ touching the others. Tailwind, giving a `bar` a brand-colored top edge and
176
+ tighter text:
177
+
178
+ ```tsx
179
+ <ConsentProvider
180
+ options={{
181
+ mode,
182
+ presentation: { prompt: { variant: 'bar' } },
183
+ components: {
184
+ banner: {
185
+ root: { className: 'group' },
186
+ card: {
187
+ className:
188
+ 'group-data-[variant=bar]:border-t-4 group-data-[variant=bar]:border-t-emerald-600',
189
+ },
190
+ },
191
+ description: {
192
+ banner: { className: 'group-data-[variant=bar]:text-xs' },
193
+ },
194
+ },
195
+ }}
196
+ />
197
+ ```
198
+
199
+ `data-variant` sits on the root, so child slots use Tailwind's `group`
200
+ prefix to read it.
201
+
202
+ [Class names and CSS-in-JS](../../customization/class-names.md) shows CSS
203
+ Modules, vanilla-extract, StyleX and Emotion classes on a part.
204
+
205
+ Set `noStyle` on a component to drop the built-in classes from every part and
206
+ keep only what your slots pass. Set `noStyle` in the provider options to do it
207
+ for every component.
208
+
209
+ [Component parts](../../customization/slots.md) lists every part key under
210
+ `components` and the `data-*` attributes each part carries. Action buttons
211
+ carry `data-action`, so a single rule such as `[data-action='dismiss']`
212
+ styles one role across every surface. Right links are underlined text by
213
+ default, so the only button on a notice is the primary action.
214
+
215
+ [Component parts](../../customization/slots.md) lists every component's part keys
216
+ and when to drop the built-in styles with `noStyle`. The `ConsentGate`
217
+ placeholder's parts are `components['consent-gate'].root`, `.title` and
218
+ `.button`.
219
+ [Class names and CSS-in-JS](../../customization/class-names.md) covers CSS
220
+ Modules, vanilla-extract, StyleX and Emotion, and
221
+ [Tailwind CSS](../../customization/tailwind.md) covers utilities on parts.
222
+
223
+ ## Turn on dark mode and change motion
224
+
225
+ Pass `colorScheme` in the provider options and the same value to
226
+ `ConsentTheme`. Leave it unset to follow a `dark` class on `<html>`, and add
227
+ dark colors with `theme.dark`. [Dark mode](../../customization/dark-mode.md)
228
+ covers each value and a dark first paint.
229
+
230
+ `disableAnimation` in the provider options turns off the banner and dialog
231
+ animations, and the same prop on `ConsentBanner` or `ConsentDialog` overrides
232
+ it for one surface. [Motion and animation](../../customization/motion.md) covers
233
+ the duration and easing tokens and reduced motion.
234
+
235
+ ## Change the copy
236
+
237
+ Labels, descriptions and languages come from the translations in your Inth
238
+ project and from the provider's `i18n` option. For the visitor's language,
239
+ `i18n.messages` replace the project's copy key by key, and keys you leave out
240
+ keep the project's wording.
241
+ [Copy and translations](../../customization/translations.md) shows the message
242
+ keys. For one banner, `ConsentBanner` also accepts text props such as
243
+ `dismissButtonText`.
244
+
245
+ ## Check the result
246
+
247
+ Clear site data and reload under a policy that shows a banner. Check the banner
248
+ and the preferences dialog, since both read the same tokens. Test a narrow
249
+ window, keyboard focus and contrast on the primary button. In development, the
250
+ console warns if `theme` holds tokens but the page has no `c15t-theme` style.
@@ -0,0 +1,89 @@
1
+ ---
2
+ title: Embeds
3
+ description: Keep YouTube videos, maps and other iframes out of a React page
4
+ until their consent category is allowed, with ConsentGate or the iframe
5
+ blocker in ConsentProvider.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Pick a method
10
+
11
+ | Method | Use it when |
12
+ | ------------------ | -------------------------------------------------------------------------------------------------------- |
13
+ | `ConsentGate` | You render the iframe in a React component and want a placeholder until the visitor allows its category. |
14
+ | The iframe blocker | The iframe comes from markup you do not render with React, such as CMS or Markdown content. |
15
+
16
+ Both keep the iframe's `src` out of the page until the category is allowed, so
17
+ the embed's host receives no request before consent.
18
+
19
+ ## Gate an embed with ConsentGate
20
+
21
+ Wrap the iframe in `ConsentGate` anywhere inside `ConsentProvider`:
22
+
23
+ ```tsx title="src/video-embed.tsx"
24
+ import { ConsentDialogLink, ConsentGate } from 'c15t/react';
25
+
26
+ export const VideoEmbed = () => (
27
+ <ConsentGate
28
+ category="measurement"
29
+ placeholder={
30
+ <div className="placeholder">
31
+ <p>Allow measurement to load this YouTube video.</p>
32
+ <ConsentDialogLink>Choose video permissions</ConsentDialogLink>
33
+ </div>
34
+ }
35
+ >
36
+ <iframe
37
+ title="YouTube video"
38
+ sandbox="allow-scripts allow-same-origin allow-presentation"
39
+ src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
40
+ allow="encrypted-media; picture-in-picture"
41
+ allowFullScreen
42
+ />
43
+ </ConsentGate>
44
+ );
45
+ ```
46
+
47
+ The iframe is absent from the page until the visitor allows measurement. The
48
+ `placeholder` shows a message and a
49
+ [`ConsentDialogLink`](./components/consent-dialog-link.md)
50
+ until then. When the visitor withdraws measurement, React removes the iframe.
51
+
52
+ Pick the category that matches what the embed does. The example uses
53
+ measurement for YouTube because its player measures views. A map or chat
54
+ widget usually belongs under functionality or experience.
55
+ [ConsentGate](./components/consent-gate.md) documents the
56
+ built-in placeholder and every prop.
57
+
58
+ ## Gate an iframe with the iframe blocker
59
+
60
+ `ConsentProvider` runs the iframe blocker by default. Give an iframe
61
+ `data-src` instead of `src`, and a `data-category`:
62
+
63
+ ```html title="Markup from your CMS"
64
+ <iframe
65
+ data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
66
+ data-category="functionality"
67
+ title="Store map"
68
+ ></iframe>
69
+ ```
70
+
71
+ c15t sets `src` from `data-src` when the category is allowed and removes `src`
72
+ when the category is withdrawn. It watches the page, so iframes added later
73
+ are handled too. Add `data-vendor` with a vendor ID to also block the iframe
74
+ while the visitor has turned that vendor off.
75
+
76
+ To turn the blocker off, set `iframeBlocker: false` in the provider `options`.
77
+
78
+ ## Verify the embeds
79
+
80
+ Open the production build in a private window with DevTools open, under a
81
+ policy that asks for consent.
82
+
83
+ 1. No iframe has a `src` pointing at `youtube-nocookie.com` or
84
+ `google.com/maps`, and the Network panel shows no request to them.
85
+ 2. Allow the embed's category and save. The iframe loads.
86
+ 3. Withdraw the category and save. The page reloads without the embed.
87
+
88
+ The [YouTube](../../integrations/youtube.md) and
89
+ [Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.
@@ -0,0 +1,163 @@
1
+ ---
2
+ title: IAB GPP
3
+ description: Add the IAB Global Privacy Platform API (__gpp) to a React app with
4
+ ConsentGPP from @c15t/react/gpp, so ad tech can read US state opt-outs and the
5
+ TCF EU consent string.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## What GPP does
10
+
11
+ `ConsentGPP` from `@c15t/react/gpp` installs `window.__gpp`, the GPP 1.1 CMP
12
+ API, and keeps its GPP string in step with the visitor's choices. Prebid.js,
13
+ Google Ad Manager and other ad tech read US privacy signals from it.
14
+
15
+ ## Turn on GPP
16
+
17
+ Render `ConsentGPP` inside `ConsentProvider`:
18
+
19
+ ```tsx title="src/consent.tsx"
20
+ import { ConsentGPP } from '@c15t/react/gpp';
21
+
22
+ <ConsentProvider options={{ mode, scripts }}>
23
+ <ConsentGPP />
24
+ {children}
25
+ </ConsentProvider>;
26
+ ```
27
+
28
+ `ConsentGPP` renders nothing. It installs `__gpp` after it mounts and
29
+ removes it when it unmounts. It lives in its own entry, like
30
+ `@c15t/react/iab`, so apps that do not import it bundle none of the GPP
31
+ code. `@c15t/react` already depends on `@c15t/iab`; with the `c15t`
32
+ package, import from `c15t/react/gpp`.
33
+
34
+ The props are the GPP options listed below, for example
35
+ `<ConsentGPP usFallback="none" />`. It works the same with a runtime you
36
+ pass to `ConsentProvider` as `runtime`.
37
+
38
+ The GPP string follows the policy and location your backend resolves for the
39
+ visitor, so a US visitor's state comes from the backend. With `offline()`
40
+ mode, pass the state yourself, for example
41
+ `overrides: { country: 'US', region: 'CA' }`.
42
+
43
+ To add the TCF EU section for visitors under an `iab` policy, set up
44
+ [IAB TCF](./iab.md) as well.
45
+
46
+ ## How the policy decides the section
47
+
48
+ The policy rule c15t matched for the visitor decides whether a section
49
+ applies. The visitor's location only picks which US section carries it.
50
+
51
+ | Matched rule and visitor | Section in the GPP string |
52
+ | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
53
+ | `iab` rule, with IAB TCF set up | `tcfeuv2` (ID 2): the TC String the CMP confirmed, unchanged |
54
+ | `iab` rule, any visitor, with `tcf: false` | None; `applicableSections` is `[-1]` |
55
+ | Any other rule with the `preferences` or `opt-out` right, US visitor in a state with a section | That state's section, such as `usca` (ID 8) |
56
+ | The same rule, US visitor whose region is unknown or whose state has no section | `usnat` (ID 7), MSPA US National version 2; none with `usFallback: 'none'` |
57
+ | The same rule, `usApproach: 'national'` | `usnat` for every US visitor |
58
+ | A rule without those rights, such as `none` | None |
59
+ | A visitor outside the US under any rule other than `iab` | None |
60
+
61
+ Every `opt-in` and `opt-out` rule has the `preferences` right, and every
62
+ `opt-out` rule also has `opt-out`. A `none` rule has them only if you add
63
+ them. A rule that gives the visitor no way to opt out produces no US
64
+ section, even when the visitor sends a GPC signal.
65
+
66
+ State sections exist for California, Colorado, Connecticut, Delaware, Florida,
67
+ Iowa, Minnesota, Montana, Nebraska, New Hampshire, New Jersey, Oregon,
68
+ Tennessee, Texas, Utah and Virginia. Indiana, Kentucky, Maryland and Rhode
69
+ Island are not encoded yet: their published specifications start the section
70
+ with a header that the IAB reference implementation does not encode. Visitors
71
+ there get the fallback, `usnat` by default. So does a US visitor whose region
72
+ the geo headers did not supply.
73
+
74
+ The MSPA US National specification describes `usnat` for MSPA signatories
75
+ that chose the national approach. Without `mspaMode`, c15t reports the
76
+ fallback as a transaction the MSPA does not cover (`MspaCoveredTransaction:
77
+ 2`), which still carries the opt-outs to vendors such as Prebid.js. If you
78
+ reserve `usnat` for the national approach, set `usFallback: 'none'`; those
79
+ visitors then get no section until their state resolves.
80
+
81
+ ## What the US sections report
82
+
83
+ * `SaleOptOut`, `SharingOptOut` and `TargetedAdvertisingOptOut`: `1` (opted
84
+ out) when the `marketing` category is not permitted, otherwise `2`. A
85
+ refusal, a GPC signal the rule maps to `marketing` through
86
+ `privacySignals.gpc`, and an opt-in rule without a grant all count.
87
+ * The opt-out notices: `1` (given), since the rule offers an opt-out.
88
+ * The sharing or processing notice: `1` when the rule has the `disclosure`
89
+ right, otherwise `2`.
90
+ * `Gpc`: the browser's Global Privacy Control signal, in sections that have a
91
+ GPC subsection. It is reported even when the rule does not map GPC to a
92
+ category, so give US rules a `privacySignals.gpc` mapping if the opt-outs
93
+ should follow it too.
94
+ * Sensitive data and known-child consents: `0` (not applicable). c15t has no
95
+ category for them, so the string states that you do not process sensitive
96
+ data or knowingly process children's data. If you do, GPP from c15t does
97
+ not describe your processing; collect and signal that consent separately.
98
+ * `MspaCoveredTransaction`: `2` (not covered) unless you set `mspaMode`.
99
+
100
+ The notice fields are your attestation, made through the rule's rights:
101
+ c15t cannot see whether your privacy notice is on the page. Show the notices
102
+ the rule promises, including a persistent opt-out control.
103
+
104
+ ## GPP options
105
+
106
+ Every field is optional. Pass them in the `gpp` option, where `gpp: true`
107
+ uses the defaults, as props on `ConsentGPP` in React, or to `mountGPP()` in
108
+ `@c15t/browser`.
109
+
110
+ | Option | Default | Effect |
111
+ | ------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `cmpId` | the IAB CMP ID c15t holds, else `1` | CMP ID reported by `ping`. The GPP specification has string creators without a registered ID, including MSPA US National creators, use `1`. A TCF EU section needs the registered ID its TC String names. |
113
+ | `usApproach` | `'state'` | `'state'` uses the visitor's state section. `'national'` reports `usnat` for every US visitor; the MSPA reserves it for signatories that chose the national approach. |
114
+ | `usFallback` | `'usnat'` | Under the state approach, the section for a US visitor whose state is unknown or has no section: `'usnat'` or `'none'`. |
115
+ | `mspaMode` | unset | `'opt-out-option'` or `'service-provider'`. Set it only if you signed the IAB Multi-State Privacy Agreement; the transaction is then reported as covered. In service provider mode the opt-outs are reported as not applicable. |
116
+ | `optOutCategories` | `['marketing']` | Categories whose refusal reports an opt-out of sale, sharing and targeted advertising. |
117
+ | `tcf` | `true` | Include the `tcfeuv2` section under an `iab` policy. It needs IAB TCF set up as well. |
118
+
119
+ ## Check the signal
120
+
121
+ Open the browser console on a page with GPP on and call:
122
+
123
+ ```js
124
+ __gpp('ping', (data) => console.log(data));
125
+ ```
126
+
127
+ For a California visitor who has not opted out, the result includes
128
+ `applicableSections: [8]`, `signalStatus: 'ready'` and
129
+ `parsedSections.usca[0].SaleOptOut === 2`. After the visitor refuses
130
+ `marketing`, `SaleOptOut` is `1`. `__gpp('getField', cb, 'usca.SaleOptOut')`
131
+ returns the same value. Scripts in iframes reach the API through the
132
+ `__gppLocator` frame and `postMessage`.
133
+
134
+ Under an `iab` rule, `parsedSections.tcfeuv2` uses the field names of the
135
+ IAB Europe TCF section, such as `PurposeConsent`, `VendorConsent` and
136
+ `PubRestrictions`. `getSection` and `getField` answer `null` for `tcfeuv2`,
137
+ as the IAB reference implementation does: TCF vendors read consent from
138
+ events, not on demand.
139
+
140
+ `signalStatus` stays `'not ready'` while the policy is pending, while the
141
+ banner or dialog is open, while an `iab` rule has no confirmed TC String,
142
+ and while a new TC String is decoded. Listeners added with
143
+ `addEventListener` get `signalStatus: 'not ready'`, then `cmpDisplayStatus`
144
+ and `sectionChange` events, then `signalStatus: 'ready'`.
145
+
146
+ ## Ad tags that load before c15t
147
+
148
+ c15t installs `__gpp` in the browser once consent starts, after it loads the
149
+ GPP code. Vendor scripts that call `__gpp` earlier need a stub. c15t takes over
150
+ the calls and listeners a stub queued, whether the stub keeps listeners on
151
+ `__gpp.events`, as the GPP specification's sample does, or returns them from
152
+ `__gpp('events')`, as `@iabgpp/stub` does. Call `initializeGPPStub()` from
153
+ `@c15t/iab/gpp` in your first script, or use the IAB's own stub snippet.
154
+ When another stub already answers iframes, c15t leaves that to it, so each
155
+ iframe call gets one reply.
156
+
157
+ If a CMP that has already loaded owns `__gpp`, c15t leaves it in place.
158
+ The `gpp` option reports the conflict to the `onError` callback,
159
+ `ConsentGPP` logs it, and `mountGPP()` and `createGPP()` throw. Run one GPP
160
+ CMP per page.
161
+
162
+ GPP signals what the visitor chose; it does not block scripts. Gate vendor
163
+ scripts with c15t's script loading as well.
@@ -15,24 +15,25 @@ from components rendered inside that provider and import them from
15
15
  import { useConsent } from 'c15t/react';
16
16
 
17
17
  export function OptionalFeature() {
18
- const allowed = useConsent('marketing');
19
- return allowed ? <div>Optional marketing content</div> : null;
18
+ const allowed = useConsent('marketing');
19
+ return allowed ? <div>Optional marketing content</div> : null;
20
20
  }
21
21
  ```
22
22
 
23
23
  Call hooks inside the consent provider. `useConsent` reads effective permission,
24
24
  which can be true under an opt-out policy without a recorded grant.
25
25
 
26
- | Need | Hook |
27
- | --------------------------------- | ------------------------------------------ |
28
- | One category's current permission | `useConsent(category)` |
29
- | All current permissions | `useConsents()` |
30
- | Recorded per-category choices | `useExplicitChoice()` |
31
- | Whether a prompt is required | `usePromptRequirement()` |
32
- | Active policy and resolution | `usePolicyRule()`, `usePolicyResolution()` |
33
- | Save an explicit choice | `useSaveConsents()` |
34
- | Open or close a consent surface | `useSetActiveUI()` |
35
- | Acknowledge a notice | `useDismissNotice()` |
26
+ | Need | Hook |
27
+ | ----------------------------------- | ------------------------------------------ |
28
+ | One category's current permission | `useConsent(category)` |
29
+ | All current permissions | `useConsents()` |
30
+ | Recorded per-category choices | `useExplicitChoice()` |
31
+ | Whether one declared vendor may run | `useVendorAllowed(id)` |
32
+ | Whether a prompt is required | `usePromptRequirement()` |
33
+ | Active policy and resolution | `usePolicyRule()`, `usePolicyResolution()` |
34
+ | Save an explicit choice | `useSaveConsents()` |
35
+ | Open or close a consent surface | `useSetActiveUI()` |
36
+ | Acknowledge a notice | `useDismissNotice()` |
36
37
 
37
38
  ## Save from a visitor action
38
39
 
@@ -42,8 +43,12 @@ Render this component inside your existing `ConsentProvider`.
42
43
  import { useSaveConsents } from 'c15t/react';
43
44
 
44
45
  export function RejectButton() {
45
- const save = useSaveConsents();
46
- return <button type="button" onClick={() => void save('none')}>Reject optional</button>;
46
+ const save = useSaveConsents();
47
+ return (
48
+ <button type="button" onClick={() => void save('none')}>
49
+ Reject optional
50
+ </button>
51
+ );
47
52
  }
48
53
  ```
49
54
 
@@ -55,6 +60,6 @@ Use focused hooks for feature gates. For a complete custom prompt or preference
55
60
  center, use `useHeadlessConsentUI()` for surface actions and `useConsentDraft()`
56
61
  for staged choices, inside one `ConsentDraftProvider` so the save commits the
57
62
  same draft. v3 has no `useConsentManager()`; the
58
- [upgrade guide](../../../upgrade-v3.md#replace-useconsentmanager) maps each of its
59
- fields to a hook. Read [consent state](../../../guides/consent-state.md) before
63
+ [upgrade guide](../../upgrade-v3.md#replace-useconsentmanager) maps each of its
64
+ fields to a hook. Read [how consent works](../../concepts/how-consent-works.md#a-permission-is-not-a-recorded-choice) before
60
65
  treating a callback as evidence of a choice.