@c15t/react 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/AGENTS.md +6 -3
  2. package/README.md +1 -1
  3. package/dist/components/iab-consent-dialog/atoms/purpose-item.cjs +1 -1
  4. package/dist/components/iab-consent-dialog/atoms/purpose-item.js +1 -1
  5. package/dist/components/iab-consent-dialog/hooks/use-gvl-data.cjs +1 -1
  6. package/dist/components/iab-consent-dialog/hooks/use-gvl-data.js +1 -1
  7. package/dist/components/iab-consent-dialog/iab-consent-dialog.cjs +1 -1
  8. package/dist/components/iab-consent-dialog/iab-consent-dialog.js +1 -1
  9. package/dist/iab/styles.tw3.css +1 -1
  10. package/dist/version.cjs +1 -1
  11. package/dist/version.js +1 -1
  12. package/dist-types/components/consent-banner/components.d.ts +3 -3
  13. package/dist-types/components/consent-banner/consent-banner.d.ts +1 -1
  14. package/dist-types/components/consent-banner/index.d.ts +9 -9
  15. package/dist-types/components/consent-banner/policy-actions.d.ts +2 -2
  16. package/dist-types/components/consent-dialog/atoms/card.d.ts +5 -5
  17. package/dist-types/components/consent-dialog/atoms/overlay.d.ts +1 -1
  18. package/dist-types/components/consent-dialog/consent-dialog.d.ts +2 -2
  19. package/dist-types/components/consent-dialog/index.d.ts +7 -7
  20. package/dist-types/components/consent-dialog-link/index.d.ts +1 -1
  21. package/dist-types/components/consent-dialog-trigger/atoms/button.d.ts +1 -1
  22. package/dist-types/components/consent-dialog-trigger/atoms/icon.d.ts +1 -1
  23. package/dist-types/components/consent-dialog-trigger/atoms/root.d.ts +2 -2
  24. package/dist-types/components/consent-dialog-trigger/atoms/toolbar.d.ts +2 -2
  25. package/dist-types/components/consent-dialog-trigger/consent-dialog-trigger-toolbar.d.ts +1 -1
  26. package/dist-types/components/consent-dialog-trigger/consent-dialog-trigger.d.ts +5 -5
  27. package/dist-types/components/consent-dialog-trigger/index.d.ts +4 -4
  28. package/dist-types/components/consent-dialog-trigger/types.d.ts +1 -1
  29. package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +1 -1
  30. package/dist-types/components/consent-preferences-link/index.d.ts +1 -1
  31. package/dist-types/components/consent-widget/atoms/accordion.d.ts +3 -3
  32. package/dist-types/components/consent-widget/atoms/button.d.ts +1 -1
  33. package/dist-types/components/consent-widget/atoms/footer.d.ts +1 -1
  34. package/dist-types/components/consent-widget/atoms/root.d.ts +1 -1
  35. package/dist-types/components/consent-widget/consent-widget.d.ts +1 -1
  36. package/dist-types/components/consent-widget/index.d.ts +11 -11
  37. package/dist-types/components/consent-widget/policy-actions.d.ts +2 -2
  38. package/dist-types/components/consent-widget/types.d.ts +1 -1
  39. package/dist-types/components/frame/atoms.d.ts +2 -2
  40. package/dist-types/components/frame/frame.d.ts +1 -1
  41. package/dist-types/components/frame/index.d.ts +3 -3
  42. package/dist-types/components/frame/types.d.ts +1 -1
  43. package/dist-types/components/iab-consent-banner/iab-consent-banner.d.ts +2 -2
  44. package/dist-types/components/iab-consent-banner/index.d.ts +19 -19
  45. package/dist-types/components/iab-consent-dialog/atoms/purpose-item.d.ts +21 -2
  46. package/dist-types/components/iab-consent-dialog/atoms/stack-item.d.ts +1 -1
  47. package/dist-types/components/iab-consent-dialog/atoms/vendor-list.d.ts +1 -1
  48. package/dist-types/components/iab-consent-dialog/hooks/use-gvl-data.d.ts +7 -1
  49. package/dist-types/components/iab-consent-dialog/iab-consent-dialog.d.ts +5 -4
  50. package/dist-types/components/iab-consent-dialog/index.d.ts +25 -25
  51. package/dist-types/components/iab-consent-dialog/use-iab-translations.d.ts +4 -0
  52. package/dist-types/components/integrations/index.d.ts +2 -2
  53. package/dist-types/components/integrations/shared.d.ts +1 -1
  54. package/dist-types/components/integrations/youtube-embed.d.ts +1 -1
  55. package/dist-types/components/shared/policy-actions.d.ts +2 -2
  56. package/dist-types/components/shared/primitives/box.d.ts +1 -1
  57. package/dist-types/components/shared/primitives/button.d.ts +4 -4
  58. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  59. package/dist-types/components/shared/primitives/legal-links/index.d.ts +1 -1
  60. package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
  61. package/dist-types/components/shared/ui/accordion/index.d.ts +2 -2
  62. package/dist-types/components/shared/ui/animated-collapse/index.d.ts +2 -2
  63. package/dist-types/components/shared/ui/branding.d.ts +1 -1
  64. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  65. package/dist-types/components/shared/ui/button/index.d.ts +2 -2
  66. package/dist-types/components/shared/ui/collapsible/index.d.ts +1 -1
  67. package/dist-types/components/shared/ui/dialog/index.d.ts +1 -1
  68. package/dist-types/components/shared/ui/preference-item/index.d.ts +1 -1
  69. package/dist-types/components/shared/ui/switch/index.d.ts +2 -2
  70. package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
  71. package/dist-types/components/shared/ui/tabs/index.d.ts +1 -1
  72. package/dist-types/context/theme-context.d.ts +1 -1
  73. package/dist-types/headless.d.ts +9 -9
  74. package/dist-types/hooks/index.d.ts +12 -12
  75. package/dist-types/hooks/use-headless-consent-ui.d.ts +1 -1
  76. package/dist-types/hooks/use-headless-iab-consent-ui.d.ts +1 -1
  77. package/dist-types/hooks/use-styles.d.ts +1 -1
  78. package/dist-types/hooks/use-theme.d.ts +1 -1
  79. package/dist-types/iab.d.ts +4 -4
  80. package/dist-types/index.d.ts +20 -20
  81. package/dist-types/primitives/accordion.d.ts +1 -1
  82. package/dist-types/primitives/button.d.ts +1 -1
  83. package/dist-types/primitives/collapsible.d.ts +1 -1
  84. package/dist-types/primitives/dialog.d.ts +1 -1
  85. package/dist-types/primitives/preference-item.d.ts +1 -1
  86. package/dist-types/primitives/switch.d.ts +1 -1
  87. package/dist-types/primitives/tabs.d.ts +1 -1
  88. package/dist-types/primitives.d.ts +7 -7
  89. package/dist-types/providers/consent-manager-provider.d.ts +1 -1
  90. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  91. package/dist-types/server/index.d.ts +5 -5
  92. package/dist-types/types/theme/index.d.ts +2 -2
  93. package/dist-types/types/theme/style-types.d.ts +1 -1
  94. package/dist-types/utils/merge-styles.d.ts +1 -1
  95. package/dist-types/utils/test-helpers.d.ts +1 -1
  96. package/dist-types/utils/theme-utils.d.ts +1 -1
  97. package/dist-types/version.d.ts +1 -1
  98. package/docs/README.md +6 -3
  99. package/docs/frameworks/react/concepts/client-modes.md +5 -3
  100. package/docs/frameworks/react/concepts/consent-models.md +2 -2
  101. package/docs/frameworks/react/concepts/cookie-management.md +4 -0
  102. package/docs/frameworks/react/concepts/glossary.md +1 -1
  103. package/docs/frameworks/react/concepts/initialization-flow.md +3 -3
  104. package/docs/frameworks/react/concepts/policy-packs.md +4 -2
  105. package/docs/frameworks/react/hooks/use-consent-manager/overview.md +1 -1
  106. package/docs/frameworks/react/iab/consent-banner.md +2 -2
  107. package/docs/frameworks/react/iab/consent-dialog.md +12 -3
  108. package/docs/frameworks/react/iab/overview.md +18 -6
  109. package/docs/frameworks/react/iframe-blocking.md +2 -0
  110. package/docs/frameworks/react/policy-packs.md +1 -1
  111. package/docs/integrations/front-chat.md +178 -0
  112. package/docs/integrations/one-dollar-stats.md +118 -0
  113. package/docs/integrations/overview.md +3 -0
  114. package/docs/integrations/pinterest-tag.md +236 -0
  115. package/docs/shared/concepts/client-modes.md +5 -3
  116. package/docs/shared/concepts/consent-models.md +2 -2
  117. package/docs/shared/concepts/cookie-management.md +4 -0
  118. package/docs/shared/concepts/glossary.md +1 -1
  119. package/docs/shared/concepts/initialization-flow.md +3 -3
  120. package/docs/shared/concepts/policy-packs.md +4 -2
  121. package/docs/shared/react/guides/iframe-blocking.md +2 -0
  122. package/docs/shared/react/guides/policy-packs.md +1 -1
  123. package/docs/shared/react/hooks/use-consent-manager/overview.md +1 -1
  124. package/docs/shared/react/iab/consent-banner.md +1 -1
  125. package/docs/shared/react/iab/consent-dialog.md +11 -2
  126. package/docs/shared/react/iab/overview.md +16 -4
  127. package/package.json +6 -6
  128. package/readme.json +1 -1
@@ -0,0 +1,236 @@
1
+ ---
2
+ title: Pinterest Tag
3
+ description: Track conversions and build audiences for Pinterest advertising campaigns.
4
+ icon: pinterest
5
+ group: integrations
6
+ ---
7
+ Pinterest Tag is Pinterest's conversion tracking and audience building tool. It measures the actions people take on your site after seeing your Pins, so you can attribute conversions and build retargeting audiences for Pinterest Ads.
8
+
9
+ ## Official Pinterest documentation
10
+
11
+ * [Install the Pinterest Tag (Pinterest Business Help)](https://help.pinterest.com/en/business/article/install-the-pinterest-tag)
12
+ * [Pinterest Tag event codes](https://help.pinterest.com/en/business/article/add-event-codes)
13
+
14
+ ## Integrate with c15t
15
+
16
+ **React**
17
+
18
+ ```tsx
19
+ import { type ReactNode } from 'react';
20
+ import { ConsentManagerProvider } from '@c15t/react';
21
+ import { pinterestTag } from '@c15t/scripts/pinterest-tag';
22
+
23
+ const scripts = [pinterestTag({ tagId: '2613654212508' })];
24
+
25
+ export function ConsentProvider({ children }: { children: ReactNode }) {
26
+ return (
27
+ <ConsentManagerProvider
28
+ options={{
29
+ mode: 'hosted',
30
+ backendURL: 'https://your-instance.c15t.dev',
31
+ scripts,
32
+ }}
33
+ >
34
+ {children}
35
+ </ConsentManagerProvider>
36
+ );
37
+ }
38
+ ```
39
+
40
+ **Next.js**
41
+
42
+ ```tsx
43
+ 'use client';
44
+
45
+ import { type ReactNode } from 'react';
46
+ import { ConsentManagerProvider } from '@c15t/nextjs';
47
+ import { pinterestTag } from '@c15t/scripts/pinterest-tag';
48
+
49
+ const scripts = [pinterestTag({ tagId: '2613654212508' })];
50
+
51
+ export function ConsentProvider({ children }: { children: ReactNode }) {
52
+ return (
53
+ <ConsentManagerProvider
54
+ options={{
55
+ mode: 'hosted',
56
+ backendURL: '/api/c15t',
57
+ scripts,
58
+ }}
59
+ >
60
+ {children}
61
+ </ConsentManagerProvider>
62
+ );
63
+ }
64
+ ```
65
+
66
+ **JavaScript**
67
+
68
+ ```ts
69
+ import { getOrCreateConsentRuntime } from 'c15t';
70
+ import { pinterestTag } from '@c15t/scripts/pinterest-tag';
71
+
72
+ getOrCreateConsentRuntime({
73
+ mode: 'hosted',
74
+ backendURL: 'https://your-instance.c15t.dev',
75
+ scripts: [pinterestTag({ tagId: '2613654212508' })],
76
+ });
77
+ ```
78
+
79
+ ## How c15t loads it
80
+
81
+ * **Category:** `marketing` (Ads & Pixels)
82
+ * **Loads when:** marketing consent is granted
83
+ * **On revocation:** [persists](/docs/frameworks/react/script-loader#persist-after-revocation) — c15t calls `pintrk('setconsent', false)`, which stops Pinterest from sending events and clears its first-party cookies without removing the script
84
+
85
+ Before the loader runs, c15t recreates Pinterest's base code: it defines the `pintrk` queue stub with `version = "3.0"`, then queues `pintrk('load', tagId)`, `pintrk('setconsent', true)`, and `pintrk('page')`. Pinterest's `core.js` replays the queue once it loads.
86
+
87
+ ## Configure the integration
88
+
89
+ You can pass extra load options and disable the default page-visit event when you need finer control:
90
+
91
+ ```ts
92
+ pinterestTag({
93
+ tagId: '2613654212508',
94
+ loadOptions: {
95
+ em: 'hello@example.com',
96
+ },
97
+ trackPageVisit: false,
98
+ });
99
+ ```
100
+
101
+ `loadOptions` is forwarded as the third argument to `pintrk('load', tagId, loadOptions)`. Pinterest uses `em` (email) and `external_id` for enhanced match, and hashes plain values before sending them.
102
+
103
+ > ⚠️ **Warning:**
104
+ > Enhanced match values are personal data. Only pass em or external\_id when your application has separately obtained consent to share them with Pinterest — marketing consent alone does not cover this.
105
+
106
+ ## Tracking events in your app
107
+
108
+ c15t gates the Pinterest Tag from loading until `marketing` consent is granted. After consent is granted the script stays in the DOM, and c15t calls `pintrk('setconsent', false)` if consent is later revoked, so Pinterest itself suppresses events.
109
+
110
+ This means `window.pintrk` is only defined after the user has granted marketing consent at least once. Use `pinterestTagEvent()` for typed standard and custom events — it's a no-op until `pintrk` exists, so it's safe to call before consent, and it doesn't bypass consent afterwards. Guarding with `has('marketing')` is still good practice so you don't queue events Pinterest will drop:
111
+
112
+ ```tsx
113
+ import { useCallback } from 'react';
114
+ import { useConsentManager } from '@c15t/react';
115
+ import { pinterestTagEvent } from '@c15t/scripts/pinterest-tag';
116
+
117
+ function useTrackCheckout() {
118
+ const { has } = useConsentManager();
119
+ return useCallback(() => {
120
+ if (has('marketing')) {
121
+ pinterestTagEvent('checkout', {
122
+ event_id: 'eventId0001',
123
+ value: 99.99,
124
+ order_quantity: 1,
125
+ currency: 'USD',
126
+ order_id: 'X-15148',
127
+ line_items: [
128
+ {
129
+ product_name: 'Parker Boots',
130
+ product_id: '1414',
131
+ product_category: 'Shoes',
132
+ product_price: 99.99,
133
+ product_quantity: 1,
134
+ },
135
+ ],
136
+ });
137
+ }
138
+ }, [has]);
139
+ }
140
+
141
+ function CheckoutButton() {
142
+ const trackCheckout = useTrackCheckout();
143
+ return <button onClick={trackCheckout}>Complete checkout</button>;
144
+ }
145
+ ```
146
+
147
+ From plain JavaScript:
148
+
149
+ ```ts
150
+ import { getOrCreateConsentRuntime } from 'c15t';
151
+ import { pinterestTagEvent } from '@c15t/scripts/pinterest-tag';
152
+
153
+ const { consentStore } = getOrCreateConsentRuntime();
154
+
155
+ if (consentStore.getState().has('marketing')) {
156
+ pinterestTagEvent('lead', { lead_type: 'newsletter' });
157
+ }
158
+ ```
159
+
160
+ Pass `event_id` when you also send the same event through the Pinterest Conversions API so Pinterest can deduplicate the two. Product details (`product_name`, `product_id`, `product_price`, and so on) go inside `line_items`, one entry per product — Pinterest ignores them at the top level.
161
+
162
+ ### Event names
163
+
164
+ Pinterest recognises 20 event types for conversion tracking: `checkout`, `addtocart`, `pagevisit`, `signup`, `watchvideo`, `lead`, `search`, `viewcategory`, `custom`, `addpaymentinfo`, `addtowishlist`, `initiatecheckout`, `subscribe`, `viewcontent`, `contact`, `schedule`, `findlocation`, `customizeproduct`, `submitapplication`, and `starttrial`. `pinterestTagEvent()` also accepts any other string as a user-defined event, which Pinterest uses for audience targeting only — it logs a console warning for these.
165
+
166
+ Which events you need depends on the campaign type. Conversions campaigns require at least one of `signup`, `lead`, `addtocart`, or `checkout`; catalog sales, conversion insights, dynamic retargeting, and the Verified Merchant Program all require `pagevisit`, `addtocart`, and `checkout` with `currency` set. See [Add event codes](https://help.pinterest.com/en/business/article/add-event-codes) for the full matrix.
167
+
168
+ ### Debugging with a callback
169
+
170
+ Pinterest accepts an optional `(didInit, error)` callback after the event data. Pass it as the third argument to `pinterestTagEvent()` to surface tag errors during development:
171
+
172
+ ```ts
173
+ import { pinterestTagEvent } from '@c15t/scripts/pinterest-tag';
174
+
175
+ pinterestTagEvent('lead', { lead_type: 'Newsletter' }, (didInit, error) => {
176
+ if (!didInit) {
177
+ console.error('Pinterest Tag error:', error);
178
+ }
179
+ });
180
+ ```
181
+
182
+ ## Types
183
+
184
+ ### PinterestTagOptions
185
+
186
+ |Property|Value|
187
+ |:--|:--|
188
+ |Type Name|\`PinterestTagOptions\`|
189
+ |Source Path|\`./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts\`|
190
+
191
+ \*ExtractedTypeTable: Could not extract "PinterestTagOptions" from "./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
192
+
193
+ ### PinterestTagLoadOptions
194
+
195
+ |Property|Value|
196
+ |:--|:--|
197
+ |Type Name|\`PinterestTagLoadOptions\`|
198
+ |Source Path|\`./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts\`|
199
+
200
+ \*ExtractedTypeTable: Could not extract "PinterestTagLoadOptions" from "./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
201
+
202
+ ### PinterestTagEventData
203
+
204
+ |Property|Value|
205
+ |:--|:--|
206
+ |Type Name|\`PinterestTagEventData\`|
207
+ |Source Path|\`./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts\`|
208
+
209
+ \*ExtractedTypeTable: Could not extract "PinterestTagEventData" from "./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
210
+
211
+ ### PinterestTagLineItem
212
+
213
+ |Property|Value|
214
+ |:--|:--|
215
+ |Type Name|\`PinterestTagLineItem\`|
216
+ |Source Path|\`./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts\`|
217
+
218
+ \*ExtractedTypeTable: Could not extract "PinterestTagLineItem" from "./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
219
+
220
+ ### PinterestTagEventCallback
221
+
222
+ |Property|Value|
223
+ |:--|:--|
224
+ |Type Name|\`PinterestTagEventCallback\`|
225
+ |Source Path|\`./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts\`|
226
+
227
+ \*ExtractedTypeTable: Could not extract "PinterestTagEventCallback" from "./packages/scripts/src/vendors/ads-and-pixels/pinterest-tag.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
228
+
229
+ ### Script
230
+
231
+ |Property|Value|
232
+ |:--|:--|
233
+ |Type Name|\`Script\`|
234
+ |Source Path|\`./packages/core/src/libs/script-loader/types.ts\`|
235
+
236
+ \*ExtractedTypeTable: Could not extract "Script" from "./packages/core/src/libs/script-loader/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
@@ -5,7 +5,7 @@ group: reference
5
5
  ---
6
6
  c15t supports three client modes that determine how consent data is stored and synchronized. Choose the mode that matches your infrastructure:
7
7
 
8
- * **Hosted mode** - Recommended for production. Backend-backed consent with geolocation, centralized policy resolution, audit history, and offline fallback.
8
+ * **Hosted mode** - Recommended for production. Backend-backed consent with geolocation, centralized policy resolution, audit history, and a temporary fallback when the backend is unreachable.
9
9
  * **Offline mode** - Browser-only storage with no network requests. Best for local development, demos, static deployments, or controlled fallback scenarios.
10
10
  * **Custom mode** - Bring your own backend with custom endpoint handlers
11
11
 
@@ -25,7 +25,9 @@ The default mode. Connects to a c15t backend for full consent lifecycle manageme
25
25
 
26
26
  1. On page load, the client calls `/init` to fetch geolocation, jurisdiction, localized translation strings
27
27
  2. When the user grants or changes consent, it is saved locally before being synced to the backend.
28
- 3. If the backend is unreachable, it falls back to Offline mode and re-syncs with the backend when it's available
28
+ 3. If `/init` fails after retries, c15t uses a temporary opt-in fallback. Optional categories are denied during fallback, while the saved choice and its hosted policy fingerprint remain in browser storage.
29
+
30
+ A saved rejection survives an outage and recovery, including recovery to an opt-out or no-banner policy. A rejection saved during fallback also survives recovery. Grants saved during fallback are not sent to the backend and need fresh confirmation under the hosted policy. Previously valid grants can resume on a later load if the original hosted policy returns and the user has not changed their choice.
29
31
 
30
32
  **Configuration:**
31
33
 
@@ -36,7 +38,7 @@ The default mode. Connects to a c15t backend for full consent lifecycle manageme
36
38
  * The backend stays the source of truth for policy, translations, and jurisdiction logic
37
39
  * Consent decisions can be stored beyond the current browser session for audit and support workflows
38
40
  * Server-side systems can preload consent-aware behavior instead of waiting for client-only storage
39
- * If the backend is temporarily unavailable, c15t can fall back locally and re-sync later
41
+ * If the backend is temporarily unavailable, c15t falls back locally and re-syncs rejections later. Grants made during the outage wait for fresh confirmation.
40
42
 
41
43
  **Best for:** Production apps that need geolocation-based jurisdiction detection, consent record storage, and compliance audit trails.
42
44
 
@@ -9,7 +9,7 @@ c15t supports four consent models that control how consent defaults, banner visi
9
9
  |--|--|--|--|
10
10
  |`opt-in`|Explicit consent required|Blocking banner|`false` (except `necessary`)|
11
11
  |`opt-out`|Processing allowed by default|Non-blocking notice|`true` (all granted)|
12
- |`iab`|IAB TCF 2.3 for programmatic ads|TCF banner|Managed by TCF framework|
12
+ |`iab`|IAB TCF 2.4 for programmatic ads|TCF banner|Managed by TCF framework|
13
13
  |`null`|No regulation detected|No banner|`true` (all auto-granted)|
14
14
 
15
15
  There are two ways c15t determines which model applies:
@@ -39,7 +39,7 @@ When the policy has `consent.gpc: true`, the browser's Global Privacy Control si
39
39
 
40
40
  ### IAB
41
41
 
42
- IAB Transparency and Consent Framework (TCF) 2.3 mode for programmatic advertising compliance. Only activates when two conditions are met: the jurisdiction is `GDPR` or `UK_GDPR`, and `iab.enabled` is `true` in your configuration.
42
+ IAB Transparency and Consent Framework (TCF) 2.4 mode for programmatic advertising compliance. Only activates when two conditions are met: the jurisdiction is `GDPR` or `UK_GDPR`, and `iab.enabled` is `true` in your configuration.
43
43
 
44
44
  When active, IAB mode generates TC strings, registers the `__tcfapi` CMP API, and works with the Global Vendor List (GVL) for machine-readable consent signals. If `iab.enabled` is not set, GDPR jurisdictions fall back to standard opt-in.
45
45
 
@@ -14,6 +14,10 @@ There are four sources of cookies on a typical website:
14
14
  **1. c15t itself**
15
15
  c15t stores the user's consent state in a `c15t` cookie (and mirrors it to localStorage). This cookie records which categories the user consented to, when they consented, and their subject ID. This is a strictly necessary cookie - it exists so the site remembers the user's consent choice.
16
16
 
17
+ If reading `window.localStorage` or calling a storage method throws, c15t logs a warning and skips unavailable pending-submission queues. Runtime construction still completes, and cookie-backed choices can still be read and saved. In v2, a failed localStorage access check skips policy initialization and hides the banner. Optional categories without a saved choice remain denied.
18
+
19
+ If the browser also blocks cookies, it cannot retain your choice across page loads. Do not use `enabled: false` to handle storage restrictions: that option grants every category.
20
+
17
21
  **2. Third-party scripts**
18
22
  When you load a tracking script (Google Analytics, Meta Pixel, etc.), that script sets its own cookies. Google Analytics creates `_ga` and `_gid` cookies. Meta creates `_fbp` and `_fbc`. These cookies are set by the script's code running in the browser - c15t prevents them by not loading the script until the user consents.
19
23
 
@@ -20,5 +20,5 @@ group: reference
20
20
  |--|--|
21
21
  |`jurisdiction`|A privacy regulation code (e.g., `GDPR`, `CCPA`, `PIPEDA`, `QC_LAW25`) detected from the user's geolocation. Maps to a consent model. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for the full mapping table.|
22
22
  |GPC|Global Privacy Control — a browser signal (`Sec-GPC: 1`) indicating the user opts out of data sale/sharing. Honored when a policy sets `consent.gpc: true` (enabled by default in California presets).|
23
- |TCF|IAB Transparency and Consent Framework (v2.3) — a standard for programmatic advertising consent in GDPR jurisdictions. See [IAB TCF](/docs/frameworks/react/iab/overview).|
23
+ |TCF|IAB Transparency and Consent Framework (v2.4) — a standard for programmatic advertising consent in GDPR jurisdictions. See [IAB TCF](/docs/frameworks/react/iab/overview).|
24
24
  |GVL|Global Vendor List — an IAB-maintained registry of ad-tech vendors and their declared purposes, used by the TCF consent flow.|
@@ -69,9 +69,9 @@ sequenceDiagram
69
69
 
70
70
  **Init** — The store fetches the resolved policy, location, and translation data. In hosted mode this calls `GET /init` on your backend; in offline mode it resolves from `offlinePolicy.policyPacks` locally. If SSR data was passed to the provider, the network fetch is skipped entirely. See [Server-Side Utilities](/docs/frameworks/react/server-side) for SSR setup.
71
71
 
72
- **Policy resolution** — When [policy packs](/docs/frameworks/react/concepts/policy-packs) are configured, the backend resolves the right policy for the visitor based on their geo-location (region → country → fallback → default). The resolved policy determines the consent model (`opt-in`, `opt-out`, `iab`, or `none`), which categories are in scope, and what UI to show. For `opt-out` and `none` models, all categories are auto-granted — unless the resolved policy has `consent.gpc: true` and the browser sends a Global Privacy Control signal, in which case `marketing` and `measurement` are denied. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for details.
72
+ **Policy resolution** — When [policy packs](/docs/frameworks/react/concepts/policy-packs) are configured, the backend resolves the right policy for the visitor based on their geo-location (region → country → fallback → default). The resolved policy determines the consent model (`opt-in`, `opt-out`, `iab`, or `none`), which categories are in scope, and what UI to show. For `opt-out` and `none` models, categories are auto-granted only when there is no saved choice — unless the resolved policy has `consent.gpc: true` and the browser sends a Global Privacy Control signal, in which case `marketing` and `measurement` are denied. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for details.
73
73
 
74
- **Re-prompting** — If the resolved policy's material fingerprint differs from the fingerprint stored with the user's last consent, the banner is shown again. This happens automatically when you change consent-affecting fields (model, categories, scope mode, allowed actions). Presentation-only changes do not trigger re-prompts. See [Policy Packs — Re-Prompting](/docs/frameworks/react/concepts/policy-packs#re-prompting) for details.
74
+ **Re-prompting** — If the resolved policy's material fingerprint differs from the fingerprint stored with the user's last consent, previous grants become invalid, while the saved choice and its denials remain. The consent UI opens again when the resolved policy has one; under a no-banner policy, invalidated grants stay denied until the user makes a new choice. This happens automatically when you change consent-affecting fields (model, categories, scope mode, allowed actions). Presentation-only changes do not trigger re-prompts. See [Policy Packs — Re-Prompting](/docs/frameworks/react/concepts/policy-packs#re-prompting) for details.
75
75
 
76
76
  **Save** — When the user interacts with the banner or dialog, their choices are persisted to localStorage/cookies and synced to the backend (along with the `policySnapshotToken` if snapshot signing is configured). Script, iframe, and network gating rules update immediately based on the new consent state. See the [Script Loader](/docs/frameworks/react/script-loader), [Iframe Blocking](/docs/frameworks/react/iframe-blocking), and [Network Blocker](/docs/frameworks/react/network-blocker) guides for gating details.
77
77
 
@@ -85,7 +85,7 @@ The banner appears when any of these conditions are true:
85
85
  2. **Policy changed** — the material policy fingerprint differs from the fingerprint stored with the user's last consent (re-prompting)
86
86
  3. **Storage is accessible** — the browser allows localStorage (not blocked in private mode)
87
87
 
88
- If the resolved model is `none` or `opt-out` (and `ui.mode` is `none`), consents are auto-granted and the banner never appears. See [Consent Models](/docs/frameworks/react/concepts/consent-models) and [Policy Packs](/docs/frameworks/react/concepts/policy-packs) for details.
88
+ If the resolved model is `none` or `opt-out` (and `ui.mode` is `none`) and no saved choice exists, consents are auto-granted and the banner never appears. An existing saved choice keeps its denials, but grants invalidated by a policy change stay denied until the user makes a new choice. See [Consent Models](/docs/frameworks/react/concepts/consent-models) and [Policy Packs](/docs/frameworks/react/concepts/policy-packs) for details.
89
89
 
90
90
  ## Debugging the Lifecycle
91
91
 
@@ -32,7 +32,7 @@ const policies = [
32
32
  |Preset|Model|UI|Matches|
33
33
  |--|--|--|--|
34
34
  |`europeOptIn()`|`opt-in`|banner|EEA + UK countries + geo fallback|
35
- |`europeIab()`|`iab`|banner|EEA + UK countries + geo fallback (TCF 2.3)|
35
+ |`europeIab()`|`iab`|banner|EEA + UK countries + geo fallback (TCF 2.4)|
36
36
  |`californiaOptOut()`|`opt-out`|none|US-CA region|
37
37
  |`quebecOptIn()`|`opt-in`|banner|CA-QC region|
38
38
  |`worldNoBanner()`|`none`|none|default fallback|
@@ -65,7 +65,7 @@ Each policy combination produces a different consent experience:
65
65
  |`model: 'opt-out'`, `ui.mode: 'none'`|No banner, everything loads immediately — user opts out via a "Do Not Sell" link|
66
66
  |`model: 'none'`, `ui.mode: 'none'`|No banner, all categories auto-granted silently|
67
67
  |`model: 'opt-in'`, `ui.mode: 'dialog'`|Full-screen dialog, nothing loads until the user consents|
68
- |`model: 'iab'`, `ui.mode: 'banner'`|IAB TCF 2.3 banner with vendor-level controls|
68
+ |`model: 'iab'`, `ui.mode: 'banner'`|IAB TCF 2.4 banner with vendor-level controls|
69
69
 
70
70
  ## How Policy Resolution Works
71
71
 
@@ -176,6 +176,8 @@ back to the `eu` profile's `fallbackLanguage`.
176
176
 
177
177
  When you change a policy in a way that affects consent semantics — like adding a category, changing the consent model, or modifying allowed actions — c15t automatically re-prompts returning users.
178
178
 
179
+ A material policy change invalidates previous grants but keeps the user's choice record and denials. Optional categories stay denied until the user makes a new choice. The resolved policy controls whether a banner or dialog appears; a no-banner policy does not silently grant previously rejected categories. A temporary hosted initialization failure does not count as an authoritative policy change.
180
+
179
181
  This works through the **material policy fingerprint**: a hash of only the consent-affecting fields (model, categories, scope, allowed actions, grouped action layout, direction, proof settings). Presentation-only changes like copy, button styling, or scroll lock do not trigger re-prompts.
180
182
 
181
183
  |Change|Re-prompts?|
@@ -51,6 +51,8 @@ For iframes outside of React (e.g., CMS content, server-rendered HTML), add `dat
51
51
 
52
52
  When consent for the specified category is granted, c15t automatically swaps `data-src` to `src`, loading the iframe. When consent is revoked, `src` is moved back to `data-src`.
53
53
 
54
+ The `data-category` value must be one of `necessary`, `functionality`, `measurement`, `experience`, or `marketing`. An iframe with any other value, including an empty one, stays blocked, and c15t logs a warning to the console.
55
+
54
56
  ### Dynamic Iframes
55
57
 
56
58
  c15t uses a `MutationObserver` to watch for dynamically added iframes. Any iframe with `data-category` added to the DOM after initialization is automatically processed.
@@ -72,7 +72,7 @@ Available presets:
72
72
  |Preset|Model|Matches|
73
73
  |--|--|--|
74
74
  |`europeOptIn()`|`opt-in`|EEA + UK countries + geo fallback|
75
- |`europeIab()`|`iab`|EEA + UK countries + geo fallback (TCF 2.3)|
75
+ |`europeIab()`|`iab`|EEA + UK countries + geo fallback (TCF 2.4)|
76
76
  |`californiaOptOut()`|`opt-out`|US-CA region|
77
77
  |`quebecOptIn()`|`opt-in`|CA-QC region|
78
78
  |`worldNoBanner()`|`none`|default fallback|
@@ -70,5 +70,5 @@ type Model = 'opt-in' | 'opt-out' | 'iab' | null;
70
70
 
71
71
  * `'opt-in'` — Explicit consent required before tracking (GDPR)
72
72
  * `'opt-out'` — Tracking allowed by default, user can opt out (CCPA)
73
- * `'iab'` — IAB TCF 2.3 compliance mode
73
+ * `'iab'` — IAB TCF 2.4 compliance mode
74
74
  * `null` — No jurisdiction detected yet
@@ -6,7 +6,7 @@ group: reference
6
6
  > ℹ️ **Info:**
7
7
  > c15t's IAB TCF support can be used in production. Use Inth for a hosted, IAB TCF-certified CMP setup with a managed CMP ID, or register your own CMP with IAB Europe and configure your own CMP ID.
8
8
 
9
- `IABConsentBanner` is a pre-built consent banner that follows the [IAB Transparency & Consent Framework (TCF) 2.3](https://iabeurope.eu/tcf-2-0/) specification. It renders when the consent model is set to `'iab'` and includes required disclosures like partner count, purpose summaries, and legitimate interest notices.
9
+ `IABConsentBanner` is a pre-built consent banner that follows the [IAB Transparency & Consent Framework (TCF) 2.4](https://iabeurope.eu/tcf-2-0/) specification. It renders when the consent model is set to `'iab'` and includes required disclosures like partner count, purpose summaries, and legitimate interest notices.
10
10
 
11
11
  Use this component instead of `ConsentBanner` when you need IAB TCF compliance for programmatic advertising in EU jurisdictions.
12
12
 
@@ -6,7 +6,7 @@ group: reference
6
6
  > ℹ️ **Info:**
7
7
  > c15t's IAB TCF support can be used in production. Use Inth for a hosted, IAB TCF-certified CMP setup with a managed CMP ID, or register your own CMP with IAB Europe and configure your own CMP ID.
8
8
 
9
- `IABConsentDialog` is an IAB TCF 2.3 compliant consent dialog that provides a tabbed interface for managing purpose consent and vendor preferences. It includes purpose grouping via stacks, individual purpose/vendor toggles, special purpose and feature disclosures, and legitimate interest handling.
9
+ `IABConsentDialog` is an IAB TCF 2.4 compliant consent dialog that provides a tabbed interface for managing purpose consent and vendor preferences. It includes purpose grouping via stacks, individual purpose/vendor toggles, special purpose and feature disclosures, and legitimate interest handling.
10
10
 
11
11
  ## Tabs
12
12
 
@@ -19,7 +19,8 @@ Displays all IAB purposes grouped into:
19
19
  * **Standalone purposes** — Purpose 1 (Store and/or access information on a device) is always shown standalone per IAB TCF spec
20
20
  * **Stacks** — Groups of related purposes determined by the GVL. Each stack is expandable to show individual purpose toggles
21
21
  * **Special features** — Opt-in features like precise geolocation
22
- * **Essential functions** — Special purposes and features that are locked (no user toggle) because they're required for basic operation
22
+ * **Essential functions** — Special purposes, which are locked (no user toggle) because vendors need them for basic operation
23
+ * **Features** — An informational section, shown when at least one vendor declares a feature
23
24
 
24
25
  Each purpose shows:
25
26
 
@@ -29,6 +30,14 @@ Each purpose shows:
29
30
  * Legitimate interest toggle where applicable
30
31
  * Expandable vendor list
31
32
 
33
+ ### Features section
34
+
35
+ TCF Policies v5.0.b treat features as information, not choices. The Features section follows those rules:
36
+
37
+ * Under the heading, the dialog shows the IAB standard text from the vendor list (`standardTexts.features`). If the vendor list does not include it, the dialog shows the `iab.preferenceCenter.features.description` translation.
38
+ * Each feature shows its name, description, IAB illustrations under **Examples**, and the names of the vendors that use it.
39
+ * The section has no switches, lock icons or per-vendor toggles. Features only serve the purposes the user chooses elsewhere in the dialog.
40
+
32
41
  ### Vendors Tab
33
42
 
34
43
  Displays all vendors from the GVL plus any custom vendors:
@@ -5,7 +5,7 @@ group: reference
5
5
  ---
6
6
  ## What is IAB TCF?
7
7
 
8
- The [IAB Transparency & Consent Framework (TCF)](https://iabeurope.eu/tcf-2-0/) is a standardized protocol for communicating user consent choices to ad tech vendors in the programmatic advertising ecosystem. TCF 2.3 is the current version and is widely adopted across the EU/EEA.
8
+ The [IAB Transparency & Consent Framework (TCF)](https://iabeurope.eu/tcf-2-0/) is a standardized protocol for communicating user consent choices to ad tech vendors in the programmatic advertising ecosystem. TCF 2.4 is the current version and is widely adopted across the EU/EEA.
9
9
 
10
10
  When your site participates in the IAB ecosystem (ad exchanges, SSPs, DSPs, DMPs), you need to:
11
11
 
@@ -25,7 +25,7 @@ If you self-host the c15t backend or want to operate as your own CMP, register y
25
25
 
26
26
  ## How c15t Implements TCF
27
27
 
28
- c15t provides a complete IAB TCF 2.3 CMP (Consent Management Platform) implementation:
28
+ c15t provides a complete IAB TCF 2.4 CMP (Consent Management Platform) implementation:
29
29
 
30
30
  1. **Global Vendor List (GVL)** — Automatically fetched from the c15t backend. Contains the official IAB vendor registry with purposes, features, and stacks.
31
31
  2. **IABConsentBanner** — A pre-built banner showing partner count, purpose summaries, and legitimate interest notices.
@@ -57,7 +57,11 @@ Purposes are grouped into **stacks** by the GVL for a simplified UI presentation
57
57
 
58
58
  ### Special Features
59
59
 
60
- Features like precise geolocation or device scanning that require explicit opt-in beyond standard consent.
60
+ Features like precise geolocation, or identifying devices based on information actively requested, that require explicit opt-in beyond standard consent.
61
+
62
+ ### Features
63
+
64
+ Features, such as linking different devices, are means of processing that vendors use only for the purposes the user chooses. They have no consent toggle. The preference center lists them in an informational section with the IAB standard text.
61
65
 
62
66
  ### Legitimate Interest
63
67
 
@@ -67,11 +71,19 @@ Some purposes can be processed under legitimate interest rather than consent. Us
67
71
 
68
72
  Each vendor in the GVL declares which purposes it uses, whether via consent or legitimate interest. The preference center lets users toggle consent per-vendor.
69
73
 
74
+ ## Consent Scope
75
+
76
+ c15t stores TCF choices per site and browser, so every TC string it creates is service-specific. c15t does not sync TCF choices across devices.
77
+
78
+ Do not use `/consent/check` or your own account data to skip the TCF UI on another device. Reusing choices across devices is a multi-device scope, and the TCF requires you to disclose that scope in the first layer of the UI, which the prebuilt IAB components do not do.
79
+
80
+ The `isServiceSpecific` option is deprecated. TCF requires IsServiceSpecific=1 in every TC string, so c15t always encodes `true` and logs a warning if you pass `false`.
81
+
70
82
  ## Standard vs IAB Components
71
83
 
72
84
  |Feature|ConsentBanner / ConsentDialog|IABConsentBanner / IABConsentDialog|
73
85
  |--|--|--|
74
- |Consent model|opt-in / opt-out|IAB TCF 2.3|
86
+ |Consent model|opt-in / opt-out|IAB TCF 2.4|
75
87
  |Granularity|Category-level (measurement, marketing, etc.)|Purpose-level + vendor-level|
76
88
  |Vendor management|No|Yes (full GVL integration)|
77
89
  |TC String|No|Yes|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/react",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "React cookie banner, consent manager, preference center, and headless CMP components with IAB TCF and Consent Mode support.",
5
5
  "keywords": [
6
6
  "react",
@@ -163,16 +163,16 @@
163
163
  "not op_mini all"
164
164
  ],
165
165
  "dependencies": {
166
- "@c15t/ui": "2.2.1",
166
+ "@c15t/ui": "2.2.2",
167
167
  "@types/google.maps": "3.65.3",
168
- "c15t": "2.2.1"
168
+ "c15t": "2.3.0"
169
169
  },
170
170
  "devDependencies": {
171
- "@c15t/backend": "2.2.1",
172
- "@c15t/iab": "2.2.1",
171
+ "@c15t/backend": "2.3.0",
172
+ "@c15t/iab": "2.3.0",
173
173
  "@c15t/typescript-config": "0.0.1",
174
174
  "@c15t/vitest-config": "1.0.0",
175
- "@iabtechlabtcf/core": "^1.5.20",
175
+ "@iabtechlabtcf/core": "^1.5.22",
176
176
  "genversion": "3.2.0",
177
177
  "msw": "^2.14.6"
178
178
  },
package/readme.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "Headless hooks for custom consent flows",
9
9
  "Minimal configuration with TypeScript-first design",
10
10
  "Comprehensive Consent Management Platform (CMP)",
11
- "IAB TCF 2.3 UI and hooks through the @c15t/react/iab subpath",
11
+ "IAB TCF 2.4 UI and hooks through the @c15t/react/iab subpath",
12
12
  "Google Tag Manager, Google Consent Mode v2, Meta Pixel, and analytics integrations through @c15t/scripts",
13
13
  "Built-in internationalization support",
14
14
  "Seamless consent storage and tracking"