@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
@@ -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.
@@ -1,143 +1,181 @@
1
1
  ---
2
2
  title: Quickstart
3
- description: Connect a React application to Inth and add consent UI, persistence
4
- and a preferences link.
3
+ description: Add a consent banner, preferences dialog and consent-gated scripts
4
+ to a React single-page app built with Vite, using Inth as the consent backend.
5
5
  group: frameworks
6
6
  ---
7
7
 
8
- ## Connect Inth
8
+ ## Before you start
9
9
 
10
- [Create an Inth project](https://inth.com), configure policy rules and the app's
11
- allowed origin, and copy its backend endpoint. This guide uses a client-rendered
12
- React app with Vite. Applications with a framework server should use that
13
- framework's adapter for request prefetch.
10
+ This guide sets up a client-rendered React app built with Vite. The browser
11
+ loads the page, asks the consent backend for the visitor's policy, then shows
12
+ the banner or runs the scripts the visitor allowed.
14
13
 
15
- | Package manager | Command |
16
- | :-------------- | :------------------------------- |
17
- | npm | `npm install c15t @c15t/scripts` |
18
- | pnpm | `pnpm add c15t @c15t/scripts` |
19
- | yarn | `yarn add c15t @c15t/scripts` |
20
- | bun | `bun add c15t @c15t/scripts` |
14
+ * **Next.js or TanStack Start?** Use that framework's guide from the
15
+ [framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
16
+ server.
17
+ * **React Router framework mode, Remix or another server-rendered React app?**
18
+ This guide still works. The server renders the page without consent state and
19
+ the browser resolves it after hydration. Read
20
+ [rendering](./rendering.md#react-router-remix-and-other-server-rendered-apps)
21
+ for what that means for the first paint.
21
22
 
22
- Set `VITE_C15T_BACKEND_URL` to the endpoint from Inth. Vite embeds public values
23
- at build time, so rebuild when changing it. Keep management credentials out of
24
- public variables.
23
+ You need an [Inth](https://inth.com) project. In the project, set the policy
24
+ rules for your regions, add your site's origin to the trusted origins, and copy
25
+ the backend URL.
26
+ [Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
27
+ covers self-hosting and offline mode.
25
28
 
26
- ## Register consent-gated scripts
29
+ ## Install c15t
27
30
 
28
- Include measurement and marketing categories in your policy for these vendors.
29
- Remove any existing loaders for them so each script loads once.
31
+ | Package manager | Command |
32
+ | :-------------- | :------------------------------------------------ |
33
+ | npm | `npm install c15t@alpha @c15t/integrations@alpha` |
34
+ | pnpm | `pnpm add c15t@alpha @c15t/integrations@alpha` |
35
+ | yarn | `yarn add c15t@alpha @c15t/integrations@alpha` |
36
+ | bun | `bun add c15t@alpha @c15t/integrations@alpha` |
30
37
 
31
- This setup uses Vite's public environment variables. Set the Inth endpoint and
32
- add your PostHog project key, PostHog API host and X Pixel ID in `.env.local`:
38
+ `c15t` contains the React adapter at `c15t/react`. `@c15t/integrations` holds the
39
+ vendor loaders used below.
33
40
 
34
- ```dotenv title=".env.local"
35
- VITE_C15T_BACKEND_URL=
36
- VITE_POSTHOG_KEY=
37
- VITE_POSTHOG_HOST=
38
- VITE_X_PIXEL_ID=
39
- ```
40
-
41
- Copy the exact backend endpoint from Inth. The vendor IDs are optional: leaving
42
- an ID empty disables that integration. When enabling PostHog, fill in both its project key and the API host
43
- shown in your PostHog project. Restart Vite after editing these values and
44
- rebuild for deployment. Other bundlers need their own configuration mechanism.
41
+ ## Register your vendor scripts
45
42
 
46
- Create `src/scripts.ts`. PostHog waits for measurement permission with
47
- `loadMode: 'after-consent'` and disables cookieless capture with
48
- `cookieless_mode: 'never'`. X Pixel waits for marketing permission.
43
+ Create `src/scripts.ts`. PostHog waits for measurement permission and X Pixel
44
+ waits for marketing permission, so your policy must include both categories.
45
+ Remove any other PostHog or X Pixel loader from your app so each vendor loads
46
+ once.
49
47
 
50
48
  ```ts title="src/scripts.ts"
51
- import { posthog } from '@c15t/scripts/posthog';
52
- import { xPixel } from '@c15t/scripts/x-pixel';
53
-
54
- const posthogKey = import.meta.env.VITE_POSTHOG_KEY;
55
- const xPixelId = import.meta.env.VITE_X_PIXEL_ID;
49
+ import { posthog } from '@c15t/integrations/posthog';
50
+ import { xPixel } from '@c15t/integrations/x-pixel';
56
51
 
57
52
  export const scripts = [
58
- ...(posthogKey
59
- ? [
60
- posthog({
61
- apiHost: import.meta.env.VITE_POSTHOG_HOST,
62
- id: posthogKey,
63
- initOptions: { cookieless_mode: 'never' },
64
- loadMode: 'after-consent',
65
- }),
66
- ]
67
- : []),
68
- ...(xPixelId ? [xPixel({ pixelId: xPixelId })] : []),
53
+ posthog({
54
+ id: 'phc_your_project_key',
55
+ initOptions: { cookieless_mode: 'never' },
56
+ loadMode: 'after-consent',
57
+ }),
58
+ xPixel({ pixelId: 'your-pixel-id' }),
69
59
  ];
70
60
  ```
71
61
 
72
- ## Mount the provider and UI
62
+ Replace `phc_your_project_key` with your PostHog project key and
63
+ `your-pixel-id` with your X Pixel ID. PostHog uses its EU host; add
64
+ `region: 'us'` for a US project. Remove the loader for a vendor you don't use.
65
+
66
+ `loadMode: 'after-consent'` keeps the PostHog SDK off the page until the
67
+ visitor allows measurement. `cookieless_mode: 'never'` turns off PostHog's
68
+ cookieless capture. See [integrations](../../integrations/overview.md) for other
69
+ vendors.
70
+
71
+ ## Mount the provider
72
+
73
+ Create `src/consent.tsx`. `ConsentProvider` creates the consent runtime and
74
+ loads the scripts. `ConsentBanner` and `ConsentDialog` render when the policy
75
+ asks for them, and `ConsentDialogLink` keeps preferences reachable after the
76
+ banner closes.
73
77
 
74
78
  ```tsx title="src/consent.tsx"
75
- import type { ReactNode } from 'react';
76
79
  import {
77
- ConsentBanner,
78
- ConsentDialog,
79
- ConsentDialogLink,
80
- ConsentProvider,
81
- hosted,
80
+ ConsentBanner,
81
+ ConsentDialog,
82
+ ConsentDialogLink,
83
+ ConsentProvider,
84
+ hosted,
82
85
  } from 'c15t/react';
83
- import 'c15t/react/styles.css';
86
+ import type { ReactNode } from 'react';
87
+
84
88
  import { scripts } from './scripts';
85
89
 
86
- const backendURL = import.meta.env.VITE_C15T_BACKEND_URL;
87
- if (!backendURL) throw new Error('Set VITE_C15T_BACKEND_URL');
88
- const mode = hosted({ url: backendURL });
89
-
90
- export function Consent({ children }: { children: ReactNode }) {
91
- return (
92
- <ConsentProvider options={{ mode, scripts }}>
93
- {children}
94
- <ConsentBanner />
95
- <ConsentDialog />
96
- <footer><ConsentDialogLink>Privacy settings</ConsentDialogLink></footer>
97
- </ConsentProvider>
98
- );
99
- }
90
+ import 'c15t/react/styles.css';
91
+
92
+ const mode = hosted({ url: 'https://your-project.inth.app' });
93
+
94
+ export const Consent = ({ children }: { children: ReactNode }) => (
95
+ <ConsentProvider options={{ mode, scripts }}>
96
+ {children}
97
+ <ConsentBanner />
98
+ <ConsentDialog />
99
+ <footer>
100
+ <ConsentDialogLink>Privacy settings</ConsentDialogLink>
101
+ </footer>
102
+ </ConsentProvider>
103
+ );
100
104
  ```
101
105
 
106
+ Replace `https://your-project.inth.app` with your project's backend URL,
107
+ exactly as Inth shows it, including any path prefix.
108
+
109
+ Wrap your app with it in `src/main.tsx`:
110
+
102
111
  ```tsx title="src/main.tsx"
103
112
  import { createRoot } from 'react-dom/client';
104
- import App from './App';
113
+
114
+ import { App } from './app';
105
115
  import { Consent } from './consent';
106
116
 
107
117
  const root = document.getElementById('root');
108
- if (!root) throw new Error('Missing #root element');
109
- createRoot(root).render(<Consent><App /></Consent>);
118
+ if (!root) {
119
+ throw new Error('Missing #root element');
120
+ }
121
+ createRoot(root).render(
122
+ <Consent>
123
+ <App />
124
+ </Consent>
125
+ );
110
126
  ```
111
127
 
112
- Keep `Consent` mounted across navigation. The banner appears when the resolved
113
- policy requires a prompt; not every region or returning visitor needs one.
114
- The dialog link keeps preferences reachable after the prompt closes.
128
+ Keep `Consent` mounted for the life of the app. If you use a client-side
129
+ router, render the router inside `Consent` so navigation does not remount the
130
+ provider. The stylesheet import in `src/consent.tsx` styles the banner and
131
+ dialog; import it once.
132
+
133
+ ## Gate your own features
115
134
 
116
- ## Gate optional functionality
135
+ Read a category's permission with `useConsent` inside the provider:
117
136
 
118
- ```tsx
137
+ ```tsx title="src/marketing-banner.tsx"
119
138
  import { useConsent } from 'c15t/react';
120
139
 
121
- export function MarketingContent() {
122
- return useConsent('marketing') ? <div>Optional marketing content</div> : null;
140
+ export function MarketingBanner() {
141
+ return useConsent('marketing') ? <aside>Spring sale</aside> : null;
123
142
  }
124
143
  ```
125
144
 
126
- This controls the rendered component. Move vendor SDKs into the
127
- [script loader](./script-loader.md) too. An independently
128
- loaded analytics script does not wait merely because a consent provider exists.
129
-
130
- `useConsent` returns effective permission, not proof of an explicit grant.
131
- Read [consent state](../../guides/consent-state.md) when recording choice events.
132
-
133
- ## Verify and customize
134
-
135
- Reject under an opt-in policy, reload and confirm optional requests stay blocked.
136
- Reopen preferences, save a different choice and test navigation. Follow
137
- [verification](../../guides/verify-consent.md), then
138
- [customization](../../customization/overview.md) to apply your design.
139
-
140
- ## Run the complete example
141
-
142
- Try the [runnable example](https://c15t.com/docs/examples) with PostHog, X Pixel, a consent-gated
143
- YouTube video and a persistent preferences control. Use it to test rejection, permission changes and reloads.
145
+ `useConsent` returns the permission right now. Under an opt-out policy it can be
146
+ `true` before the visitor has chosen anything. Read
147
+ [how consent works](../../concepts/how-consent-works.md#a-permission-is-not-a-recorded-choice)
148
+ before you record or report choices. For iframes, use
149
+ [`ConsentGate`](./components/consent-gate.md).
150
+
151
+ ## Check that it works
152
+
153
+ Build and preview the production bundle with `vite build` and `vite preview`,
154
+ then open the site in a private window with DevTools open on the Network tab.
155
+ Test under a policy that asks for a choice, such as an EU opt-in policy.
156
+
157
+ 1. **Before a choice.** The banner shows. There are no requests to
158
+ `posthog.com` or `static.ads-twitter.com`.
159
+ 2. **Reject All.** The banner closes and the vendor requests stay absent.
160
+ 3. **Reload.** The banner stays closed and the vendor requests stay absent.
161
+ 4. **Privacy settings.** The footer link opens the dialog with **Analytics** (the
162
+ `measurement` category) and **Marketing** off. Turn on **Analytics** and save. PostHog's `array.js` loads and
163
+ X Pixel stays absent.
164
+ 5. **Turn Analytics off again.** The page reloads and PostHog does not load.
165
+
166
+ If the banner never appears, check the Network tab for the request to
167
+ `/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
168
+ covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
169
+ full release checklist.
170
+
171
+ ## Next steps
172
+
173
+ * [Rendering](./rendering.md) for server-rendered React apps and
174
+ what the first paint contains.
175
+ * [Scripts and embeds](./scripts.md) for more vendors, network
176
+ blocking and data cleanup.
177
+ * [Customize](./customize.md) for colors, fonts, layout and copy.
178
+ * [Components](./components/consent-provider.md) for
179
+ every provider option and component prop.
180
+ * The runnable app in `examples/react` of the
181
+ [c15t repository](https://github.com/c15t/c15t) contains this setup.