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

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 (183) hide show
  1. package/AGENTS.md +91 -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/components/consent-gate/atoms.js +1 -1
  6. package/dist/components/consent-gate/consent-gate.js +1 -1
  7. package/dist/components/iab-panel/atoms/card.js +1 -1
  8. package/dist/components/iab-panel/atoms/feature-item.js +2 -0
  9. package/dist/components/iab-panel/atoms/header.js +1 -1
  10. package/dist/components/iab-panel/iab-panel.js +1 -1
  11. package/dist/components/iab-panel/index.js +1 -1
  12. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  13. package/dist/components/iab-prompt/atoms/root.js +1 -1
  14. package/dist/components/panel/atoms/card.js +1 -1
  15. package/dist/components/panel/atoms/root.js +1 -1
  16. package/dist/components/panel-trigger/atoms/button.js +1 -1
  17. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  18. package/dist/components/preferences/atoms/accordion.js +1 -1
  19. package/dist/components/prompt/atoms/overlay.js +1 -1
  20. package/dist/components/prompt/atoms/root.js +1 -1
  21. package/dist/components/prompt/components.js +1 -1
  22. package/dist/components/shared/libs/compose-refs.js +1 -0
  23. package/dist/components/shared/libs/slot.js +1 -1
  24. package/dist/components/shared/primitives/button.js +1 -1
  25. package/dist/components/shared/ui/dialog/dialog.js +1 -1
  26. package/dist/components/shared/ui/icon.js +1 -1
  27. package/dist/context/consent-tracking-context.js +1 -1
  28. package/dist/devtools.js +1 -1
  29. package/dist/draft.js +1 -1
  30. package/dist/hooks/use-focus-trap.js +1 -1
  31. package/dist/hooks.js +1 -1
  32. package/dist/iab/styles.tw3.css +4 -4
  33. package/dist/index.js +1 -1
  34. package/dist/module-hooks/iframe-blocker.js +1 -1
  35. package/dist/postcss-tailwind3.js +1 -0
  36. package/dist/provider.js +1 -1
  37. package/dist/server/fetch-ssr-data.js +1 -1
  38. package/dist/server/headers.js +1 -1
  39. package/dist/server/normalize-url.js +1 -1
  40. package/dist/styles.tw3.css +16 -16
  41. package/dist/transports/offline.js +1 -1
  42. package/dist/ui-save.js +1 -1
  43. package/dist/utils/merge-slot-props.js +1 -1
  44. package/dist/utils/theme-utils.js +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
  47. package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
  48. package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
  49. package/dist-types/components/iab-panel/index.d.ts +5 -2
  50. package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
  51. package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
  52. package/dist-types/components/iab-prompt/index.d.ts +2 -2
  53. package/dist-types/components/panel/atoms/card.d.ts +1 -0
  54. package/dist-types/components/prompt/components.d.ts +6 -0
  55. package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
  56. package/dist-types/components/shared/ui/icon.d.ts +2 -1
  57. package/dist-types/context/consent-tracking-context.d.ts +10 -0
  58. package/dist-types/context/theme-context.d.ts +2 -2
  59. package/dist-types/context.d.ts +6 -0
  60. package/dist-types/devtools.d.ts +13 -2
  61. package/dist-types/draft.d.ts +10 -4
  62. package/dist-types/hooks/use-focus-trap.d.ts +3 -1
  63. package/dist-types/hooks.d.ts +54 -13
  64. package/dist-types/iab.d.ts +1 -1
  65. package/dist-types/index.d.ts +2 -2
  66. package/dist-types/postcss-tailwind3.d.ts +21 -0
  67. package/dist-types/provider.d.ts +22 -5
  68. package/dist-types/server/headers.d.ts +14 -4
  69. package/dist-types/server/normalize-url.d.ts +14 -7
  70. package/dist-types/server/types.d.ts +12 -1
  71. package/dist-types/transports/offline.d.ts +7 -16
  72. package/dist-types/ui-config-context.d.ts +7 -1
  73. package/dist-types/utils/merge-slot-props.d.ts +15 -1
  74. package/dist-types/utils/theme-utils.d.ts +17 -2
  75. package/dist-types/version.d.ts +1 -1
  76. package/docs/README.md +91 -72
  77. package/docs/assets/v3/bottom-bar.png +0 -0
  78. package/docs/assets/v3/brand-card.png +0 -0
  79. package/docs/assets/v3/brand-preferences.png +0 -0
  80. package/docs/assets/v3/choice-wall.png +0 -0
  81. package/docs/assets/v3/headless-bar-html.png +0 -0
  82. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  83. package/docs/assets/v3/headless-bar.png +0 -0
  84. package/docs/assets/v3/slim-bar.png +0 -0
  85. package/docs/concepts/choose-your-setup.md +87 -0
  86. package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
  87. package/docs/{guides → concepts}/consent-state.md +71 -101
  88. package/docs/{guides → concepts}/data-fetching.md +31 -27
  89. package/docs/concepts/how-consent-works.md +123 -0
  90. package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
  91. package/docs/customization/class-names.md +202 -0
  92. package/docs/customization/dark-mode.md +157 -0
  93. package/docs/customization/motion.md +119 -0
  94. package/docs/customization/overview.md +67 -34
  95. package/docs/customization/recipes.md +839 -49
  96. package/docs/customization/slots.md +216 -35
  97. package/docs/customization/stylesheets.md +147 -0
  98. package/docs/customization/tailwind.md +842 -0
  99. package/docs/customization/tokens.md +163 -96
  100. package/docs/customization/translations.md +61 -3
  101. package/docs/frameworks/react/callbacks.md +118 -0
  102. package/docs/frameworks/react/clear-on-revocation.md +106 -0
  103. package/docs/frameworks/react/components/consent-banner.md +42 -28
  104. package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
  105. package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
  106. package/docs/frameworks/react/components/consent-dialog.md +56 -50
  107. package/docs/frameworks/react/components/consent-gate.md +37 -20
  108. package/docs/frameworks/react/components/consent-provider.md +126 -0
  109. package/docs/frameworks/react/components/consent-widget.md +23 -20
  110. package/docs/frameworks/react/components/dev-tools.md +52 -84
  111. package/docs/frameworks/react/components.md +105 -0
  112. package/docs/frameworks/react/compose.md +354 -0
  113. package/docs/frameworks/react/content-security-policy.md +126 -0
  114. package/docs/frameworks/react/customize.md +250 -0
  115. package/docs/frameworks/react/embeds.md +89 -0
  116. package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
  117. package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
  118. package/docs/frameworks/react/network-blocker.md +35 -41
  119. package/docs/frameworks/react/quickstart.md +139 -101
  120. package/docs/frameworks/react/rendering.md +128 -0
  121. package/docs/frameworks/react/scripts.md +115 -0
  122. package/docs/frameworks/react/translations.md +155 -0
  123. package/docs/frameworks/react/troubleshooting.md +92 -47
  124. package/docs/frameworks/react/vendor-consent.md +170 -0
  125. package/docs/guides/banner-experiments.md +654 -0
  126. package/docs/guides/troubleshooting.md +120 -47
  127. package/docs/guides/verify-consent.md +81 -49
  128. package/docs/integrations/adobe-analytics.md +167 -159
  129. package/docs/integrations/ahrefs-analytics.md +152 -154
  130. package/docs/integrations/amplitude.md +162 -156
  131. package/docs/integrations/building-integrations.md +136 -37
  132. package/docs/integrations/clearbit.md +154 -154
  133. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  134. package/docs/integrations/cloudflare-zaraz.md +209 -261
  135. package/docs/integrations/crisp.md +164 -158
  136. package/docs/integrations/databuddy.md +157 -173
  137. package/docs/integrations/fathom-analytics.md +158 -156
  138. package/docs/integrations/front-chat.md +167 -167
  139. package/docs/integrations/google-maps.md +118 -83
  140. package/docs/integrations/google-tag-manager.md +178 -163
  141. package/docs/integrations/google-tag.md +163 -160
  142. package/docs/integrations/heap.md +163 -155
  143. package/docs/integrations/hightouch.md +161 -157
  144. package/docs/integrations/hotjar.md +159 -155
  145. package/docs/integrations/intercom.md +183 -153
  146. package/docs/integrations/klaviyo.md +486 -0
  147. package/docs/integrations/linkedin-insights.md +174 -150
  148. package/docs/integrations/logrocket.md +160 -156
  149. package/docs/integrations/matomo-analytics.md +188 -178
  150. package/docs/integrations/meta-pixel.md +188 -150
  151. package/docs/integrations/microsoft-clarity.md +163 -155
  152. package/docs/integrations/microsoft-uet.md +148 -154
  153. package/docs/integrations/mixpanel-analytics.md +155 -160
  154. package/docs/integrations/one-dollar-stats.md +166 -165
  155. package/docs/integrations/openai-pixel.md +204 -301
  156. package/docs/integrations/overview.md +137 -80
  157. package/docs/integrations/pinterest-tag.md +191 -183
  158. package/docs/integrations/pirsch.md +169 -159
  159. package/docs/integrations/plausible-analytics.md +172 -158
  160. package/docs/integrations/posthog.md +226 -241
  161. package/docs/integrations/promptwatch.md +154 -154
  162. package/docs/integrations/reddit-pixel.md +185 -157
  163. package/docs/integrations/rudderstack.md +201 -186
  164. package/docs/integrations/rybbit-analytics.md +171 -160
  165. package/docs/integrations/segment.md +182 -154
  166. package/docs/integrations/snapchat-pixel.md +186 -156
  167. package/docs/integrations/tiktok-pixel.md +171 -150
  168. package/docs/integrations/umami-analytics.md +163 -158
  169. package/docs/integrations/vercel-analytics.md +166 -157
  170. package/docs/integrations/x-pixel.md +176 -150
  171. package/docs/integrations/youtube.md +121 -86
  172. package/docs/upgrade-v3.md +475 -467
  173. package/package.json +15 -9
  174. package/docs/assets/v3/brand-bar.png +0 -0
  175. package/docs/assets/v3/mobile-card.png +0 -0
  176. package/docs/assets/v3/preferences.png +0 -0
  177. package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
  178. package/docs/frameworks/react/script-loader.md +0 -69
  179. package/docs/frameworks/react/styling/overview.md +0 -266
  180. package/docs/guides/deployment-modes.md +0 -75
  181. package/docs/guides/shared-consent-controls.md +0 -158
  182. package/docs/integrations/clear-on-revocation.md +0 -167
  183. 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.
@@ -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.
@@ -11,6 +11,12 @@ Use the IAB integration when your application needs the Transparency and Consent
11
11
  Framework. Configure an IAB policy, the correct CMP identity and vendor data for
12
12
  your deployment. A demo CMP ID is not a production configuration.
13
13
 
14
+ A CMP ID is assigned to a company when it registers as a CMP with IAB Europe
15
+ ([register.consensu.org/CMP](https://register.consensu.org/CMP)). Every TC
16
+ String names the CMP that wrote it, so an ID you did not register attributes
17
+ your users' consent records to someone else. Omitting it leaves the add-on
18
+ unmounted: no `__tcfapi`, and no TC String written.
19
+
14
20
  Install `@c15t/iab` alongside your framework adapter, then mount the IAB
15
21
  components inside an existing consent provider connected to Inth.
16
22
 
@@ -21,15 +27,19 @@ stylesheet holds only the IAB rules and uses the tokens from the base
21
27
  stylesheet.
22
28
 
23
29
  ```tsx title="src/iab-consent.tsx"
24
- import { IABProvider, IABConsentBanner, IABConsentDialog } from 'c15t/react/iab';
30
+ import {
31
+ IABProvider,
32
+ IABConsentBanner,
33
+ IABConsentDialog,
34
+ } from 'c15t/react/iab';
25
35
 
26
36
  export function IABConsent({ cmpId }: { cmpId: number }) {
27
- return (
28
- <IABProvider cmpId={cmpId}>
29
- <IABConsentBanner />
30
- <IABConsentDialog />
31
- </IABProvider>
32
- );
37
+ return (
38
+ <IABProvider cmpId={cmpId}>
39
+ <IABConsentBanner />
40
+ <IABConsentDialog />
41
+ </IABProvider>
42
+ );
33
43
  }
34
44
  ```
35
45
 
@@ -44,7 +54,7 @@ Global Vendor List response before changing rendering conditions.
44
54
 
45
55
  IAB presentation follows `presentation.prompt.blocking` and
46
56
  `presentation.preferences.blocking`. A blocking dialog needs focus management,
47
- scroll locking and a backdrop together. See [migration](../../../upgrade-v3.md) for
57
+ scroll locking and a backdrop together. See [migration](../../upgrade-v3.md) for
48
58
  legacy option behavior.
49
59
 
50
60
  ## Verify vendor and purpose choices
@@ -62,22 +72,26 @@ placeholders; use vendors from your list whose declarations allow each
62
72
  restriction.
63
73
 
64
74
  ```tsx title="src/iab-consent.tsx"
65
- import { IABProvider, IABConsentBanner, IABConsentDialog } from 'c15t/react/iab';
75
+ import {
76
+ IABProvider,
77
+ IABConsentBanner,
78
+ IABConsentDialog,
79
+ } from 'c15t/react/iab';
66
80
 
67
81
  const publisherRestrictions = [
68
- // Vendors 10 and 755 may not use purpose 4.
69
- { purposeId: 4, restrictionType: 0 as const, vendorIds: [10, 755] },
70
- // Vendor 755 must use legitimate interest for purpose 7.
71
- { purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
82
+ // Vendors 10 and 755 may not use purpose 4.
83
+ { purposeId: 4, restrictionType: 0 as const, vendorIds: [10, 755] },
84
+ // Vendor 755 must use legitimate interest for purpose 7.
85
+ { purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
72
86
  ];
73
87
 
74
88
  export function IABConsent({ cmpId }: { cmpId: number }) {
75
- return (
76
- <IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
77
- <IABConsentBanner />
78
- <IABConsentDialog />
79
- </IABProvider>
80
- );
89
+ return (
90
+ <IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
91
+ <IABConsentBanner />
92
+ <IABConsentDialog />
93
+ </IABProvider>
94
+ );
81
95
  }
82
96
  ```
83
97
 
@@ -90,8 +104,9 @@ Each restriction type has a requirement that depends on the vendor list:
90
104
  | `2` | Legitimate interest required | declares the purpose for consent and lists it in `flexiblePurposes` |
91
105
 
92
106
  Type `2` is never allowed for purposes 1, 3, 4, 5 and 6, which TCF allows
93
- only with consent. Restrictions also require a service-specific CMP, the
94
- default. Setting `isServiceSpecific: false` with restrictions is an error.
107
+ only with consent. TCF allows restrictions only in service-specific TC strings.
108
+ c15t always writes those, so the deprecated `isServiceSpecific` option has no
109
+ effect on restrictions.
95
110
  c15t copies the restrictions when the CMP starts, so changing your array
96
111
  afterwards has no effect.
97
112
 
@@ -140,7 +155,7 @@ objection control is the opt-out. Such a purpose does not decide its c15t
140
155
  category: the category follows the purposes in it that the visitor can
141
156
  consent to. Legitimate interest never grants a category on its own. If none of
142
157
  a category's purposes has a consent basis, the category needs all of them
143
- consented, which only Accept all does. Stack switches
158
+ consented, which only Accept All does. Stack switches
144
159
  cover only the purposes some vendor processes on consent. Display-model rows
145
160
  expose this as `hasConsentBasis`. Custom preference UIs can call
146
161
  `applyPublisherRestrictionsToGVL` from `@c15t/iab/headless`, or pass
@@ -162,12 +177,46 @@ Types `1` and `2` block the target when the vendor list does not mark the
162
177
  purpose as flexible for that vendor. Targets without a `vendorId` ignore
163
178
  restrictions. A target that uses only legitimate interest once restrictions
164
179
  apply is not blocked by a refused category, since it needs no consent; its
165
- legitimate interest signals and the visitor's objection decide. GPC, opt-out
166
- directives and strict scope still block it, and the category stays refused for
167
- scripts that depend on it. Accept all grants the vendor signal a restriction
168
- moves a purpose to, including legitimate interest for a vendor that declares
169
- none. Legitimate interest a restriction introduces applies until the visitor
170
- objects: the preference centres show it as allowed, and saving without touching
171
- it encodes it as allowed. `introducedLegitimateInterest` from
172
- `@c15t/iab/headless` lists those purposes and vendors for custom preference
173
- UIs.
180
+ legitimate interest signals and the visitor's objection decide. GPC and strict
181
+ scope still block it, and the category stays refused for scripts that depend on
182
+ it. Accept All grants the vendor signal a restriction moves a purpose to,
183
+ including legitimate interest for a vendor that declares none. Legitimate
184
+ interest a restriction introduces applies until the visitor objects: the
185
+ preference centres show it as allowed, and saving without touching it encodes
186
+ it as allowed. `introducedLegitimateInterest` from `@c15t/iab/headless` lists
187
+ those purposes and vendors for custom preference UIs.
188
+
189
+ ## How the preference centre shows features
190
+
191
+ The stock IAB preference centres in React, Next.js, Vue, Svelte, Astro and the
192
+ script tag follow TCF 2.4 and Policies v5.0.b. Special purposes stay in their
193
+ locked section with a lock icon, because the visitor cannot object to them.
194
+
195
+ Features get their own section after the special purposes. It shows the IAB
196
+ standard text for features from the Global Vendor List's
197
+ `standardTexts.features`. If the list has no standard text, it shows the
198
+ `iab.preferenceCenter.features.description` translation. Each feature lists
199
+ its name, description, illustrations and the vendors that use it. The section has no switch, lock or vendor toggle, because
200
+ TCF forbids showing features next to a control that cannot be disabled.
201
+
202
+ The feature section follows the theme's spacing and typography tokens.
203
+ Its disclosure arrows are decorative and stay out of the accessibility tree.
204
+
205
+ A custom preference UI gets the same rows from
206
+ `resolveIABDialogDisplayModel` in `@c15t/iab/headless`. `essentialRows` holds
207
+ the special purposes only. `featureRows` holds the features, with
208
+ `locked: false` and `toggle: 'none'`, so render them without a control.
209
+ `featuresStandardText` is the standard text, or `null` when you should use the
210
+ translation.
211
+
212
+ ## Consent scope
213
+
214
+ c15t stores TCF choices per site and browser, which the TCF calls
215
+ service-specific scope. It does not sync TC strings across devices, and every
216
+ TC string it writes has `IsServiceSpecific` set to `1`.
217
+
218
+ Do not use `/consent/check` or your own account data to skip the TCF banner on
219
+ another device. That turns the choice into multi-device scope, which the TCF
220
+ requires you to disclose in the first layer, and c15t does not show that
221
+ disclosure. The `isServiceSpecific` option is deprecated. c15t ignores it and
222
+ logs a warning when you pass `false`.
@@ -1,50 +1,42 @@
1
1
  ---
2
- title: Block network requests
3
- description: Block fetch and XHR calls to third-party domains in a React app
4
- until the visitor grants their consent category.
2
+ title: Network blocker
3
+ description: Hold fetch and XMLHttpRequest calls in a React app until their
4
+ consent category is allowed, with networkBlocker rules in the ConsentProvider
5
+ options.
5
6
  group: frameworks
6
7
  ---
7
8
 
8
- ## Add rules to the provider
9
+ ## Add rules
9
10
 
10
- The network blocker stops `fetch` and `XMLHttpRequest` calls to domains you
11
- list until the visitor grants their category. Use it for beacons and API
12
- calls that bypass script loading, such as a pixel fired by an SDK that is
13
- already on the page.
11
+ Some SDKs are already on the page and send beacons or API calls of their own.
12
+ Add `networkBlocker` rules to the provider options to stop `fetch` and
13
+ `XMLHttpRequest` calls to those domains until the category is allowed. Load
14
+ vendor SDKs through [`scripts`](./scripts.md) first, so they
15
+ do not run at all before consent.
14
16
 
15
- Add `networkBlocker` to the `ConsentProvider` options from your
16
- [quickstart](./quickstart.md). Set the URL to the endpoint
17
- supplied by your Inth project.
17
+ This partial example adds the option next to the existing `mode` and
18
+ `scripts`:
18
19
 
19
- ```tsx title="components/consent.tsx"
20
- 'use client';
21
-
22
- import type { ReactNode } from 'react';
23
- import { ConsentBanner, ConsentDialog, ConsentProvider, hosted } from 'c15t/react';
24
-
25
- const mode = hosted({ url: 'https://your-project.inth.app' });
20
+ ```tsx title="src/consent.tsx"
26
21
  const networkBlocker = {
27
- rules: [
28
- { id: 'google-analytics', domain: 'google-analytics.com', category: 'measurement' },
29
- { id: 'meta-pixel', domain: 'facebook.com', pathIncludes: '/tr', category: 'marketing' },
30
- ],
22
+ rules: [
23
+ {
24
+ id: 'google-analytics',
25
+ domain: 'google-analytics.com',
26
+ category: 'measurement',
27
+ },
28
+ {
29
+ id: 'meta-pixel',
30
+ domain: 'facebook.com',
31
+ pathIncludes: '/tr',
32
+ category: 'marketing',
33
+ },
34
+ ],
31
35
  };
32
36
 
33
- export function Consent({ children }: { children: ReactNode }) {
34
- return (
35
- <ConsentProvider options={{ mode, networkBlocker }}>
36
- {children}
37
- <ConsentBanner />
38
- <ConsentDialog />
39
- </ConsentProvider>
40
- );
41
- }
37
+ <ConsentProvider options={{ mode, scripts, networkBlocker }}>
42
38
  ```
43
39
 
44
- Render your app inside `ConsentProvider`. Blocking starts when it renders, so
45
- components outside it that send requests while rendering, or modules evaluated
46
- before it, are not covered. See the limits below.
47
-
48
40
  ## Match requests with rules
49
41
 
50
42
  Each rule names a `domain` and the consent `category` a request needs. The
@@ -137,10 +129,12 @@ Keep tracking calls out of that window:
137
129
  * Check `useConsent('measurement')` (or the category you need) before you
138
130
  call a vendor from your own code, and treat the blocker as a backstop.
139
131
 
140
- ## Verify
132
+ ## Verify the blocked requests
133
+
134
+ Open DevTools Network in a private window, under a policy that asks for
135
+ consent.
141
136
 
142
- Open the browser's network panel, clear site data and reload. Requests
143
- matching a rule should not appear until you grant their category; with
144
- logging on, the console lists each blocked request. Reject, reload, and
145
- check that they still do not appear. The
146
- [verification guide](../../guides/verify-consent.md) covers automated checks.
137
+ 1. Before a choice, requests matching a rule are absent, and the console logs
138
+ each blocked request.
139
+ 2. Allow the rule's category and save. Matching requests go out.
140
+ 3. Reject, reload, and check that they stay absent.