@c15t/react 2.0.0-rc.7 → 2.0.0-rc.9

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 (138) hide show
  1. package/README.md +9 -0
  2. package/dist/components/consent-banner/consent-banner.cjs +1 -1
  3. package/dist/components/consent-banner/consent-banner.js +1 -1
  4. package/dist/components/consent-banner/index.cjs +1 -1
  5. package/dist/components/consent-banner/index.js +1 -1
  6. package/dist/components/consent-banner/policy-actions.cjs +1 -0
  7. package/dist/components/consent-banner/policy-actions.js +1 -0
  8. package/dist/components/consent-dialog/atoms/card.cjs +1 -1
  9. package/dist/components/consent-dialog/atoms/card.js +1 -1
  10. package/dist/components/consent-dialog-trigger/atoms/icon.cjs +1 -1
  11. package/dist/components/consent-dialog-trigger/atoms/icon.js +1 -1
  12. package/dist/components/consent-widget/atoms/footer.cjs +1 -1
  13. package/dist/components/consent-widget/atoms/footer.js +1 -1
  14. package/dist/components/consent-widget/consent-widget.cjs +1 -1
  15. package/dist/components/consent-widget/consent-widget.js +1 -1
  16. package/dist/components/consent-widget/index.cjs +1 -1
  17. package/dist/components/consent-widget/index.js +1 -1
  18. package/dist/components/consent-widget/policy-actions.cjs +1 -0
  19. package/dist/components/consent-widget/policy-actions.js +1 -0
  20. package/dist/components/iab-consent-banner/iab-consent-banner.cjs +1 -1
  21. package/dist/components/iab-consent-banner/iab-consent-banner.js +1 -1
  22. package/dist/components/iab-consent-dialog/iab-consent-dialog.cjs +1 -1
  23. package/dist/components/iab-consent-dialog/iab-consent-dialog.js +1 -1
  24. package/dist/components/shared/policy-actions.cjs +1 -0
  25. package/dist/components/shared/policy-actions.js +1 -0
  26. package/dist/components/shared/ui/branding.cjs +1 -0
  27. package/dist/components/shared/ui/branding.js +1 -0
  28. package/dist/components/shared/ui/logo.cjs +1 -1
  29. package/dist/components/shared/ui/logo.js +1 -1
  30. package/dist/components/shared/ui/switch/index.cjs +1 -1
  31. package/dist/components/shared/ui/switch/index.js +1 -1
  32. package/dist/hooks/index.cjs +1 -1
  33. package/dist/hooks/use-consent-manager.cjs +1 -1
  34. package/dist/hooks/use-consent-manager.js +1 -1
  35. package/dist/hooks/use-headless-consent-ui.cjs +1 -1
  36. package/dist/hooks/use-headless-consent-ui.js +1 -1
  37. package/dist/iab/styles.css +11 -1
  38. package/dist/iab/styles.tw3.css +14 -1
  39. package/dist/index.cjs +1 -1
  40. package/dist/providers/consent-manager-provider.cjs +1 -1
  41. package/dist/providers/consent-manager-provider.js +1 -1
  42. package/dist/server/fetch-ssr-data.cjs +1 -1
  43. package/dist/server/fetch-ssr-data.js +1 -1
  44. package/dist/styles.css +10 -1
  45. package/dist/styles.tw3.css +13 -1
  46. package/dist/version.cjs +1 -1
  47. package/dist/version.js +1 -1
  48. package/dist-types/components/consent-banner/atoms/root.d.ts +1 -1
  49. package/dist-types/components/consent-banner/consent-banner.d.ts +7 -2
  50. package/dist-types/components/consent-banner/index.d.ts +3 -0
  51. package/dist-types/components/consent-banner/policy-actions.d.ts +11 -0
  52. package/dist-types/components/consent-dialog/atoms/card.d.ts +7 -2
  53. package/dist-types/components/consent-dialog/atoms/root.d.ts +1 -1
  54. package/dist-types/components/consent-dialog/consent-dialog.d.ts +1 -1
  55. package/dist-types/components/consent-dialog-trigger/atoms/icon.d.ts +1 -1
  56. package/dist-types/components/consent-dialog-trigger/types.d.ts +1 -1
  57. package/dist-types/components/consent-widget/index.d.ts +3 -0
  58. package/dist-types/components/consent-widget/policy-actions.d.ts +11 -0
  59. package/dist-types/components/frame/atoms.d.ts +1 -1
  60. package/dist-types/components/frame/types.d.ts +1 -1
  61. package/dist-types/components/iab-consent-banner/atoms/root.d.ts +1 -1
  62. package/dist-types/components/iab-consent-banner/iab-consent-banner.d.ts +1 -1
  63. package/dist-types/components/iab-consent-dialog/atoms/root.d.ts +1 -1
  64. package/dist-types/components/iab-consent-dialog/atoms/vendor-list.d.ts +1 -1
  65. package/dist-types/components/iab-consent-dialog/iab-consent-dialog.d.ts +1 -1
  66. package/dist-types/components/shared/policy-actions.d.ts +29 -0
  67. package/dist-types/components/shared/primitives/button.d.ts +1 -1
  68. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  69. package/dist-types/components/shared/primitives/legal-links/index.d.ts +1 -1
  70. package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
  71. package/dist-types/components/shared/ui/accordion/accordion.types.d.ts +1 -1
  72. package/dist-types/components/shared/ui/branding.d.ts +23 -0
  73. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  74. package/dist-types/components/shared/ui/logo.d.ts +8 -0
  75. package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +1 -1
  76. package/dist-types/components/shared/ui/switch/index.d.ts +1 -1
  77. package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
  78. package/dist-types/components/shared/ui/switch/switch.types.d.ts +1 -1
  79. package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -1
  80. package/dist-types/context/consent-manager-context.d.ts +1 -1
  81. package/dist-types/headless.d.ts +1 -1
  82. package/dist-types/hooks/use-component-config.d.ts +1 -1
  83. package/dist-types/hooks/use-consent-manager.d.ts +1 -1
  84. package/dist-types/hooks/use-headless-consent-ui.d.ts +1 -1
  85. package/dist-types/hooks/use-ssr-status.d.ts +2 -1
  86. package/dist-types/hooks/use-translations.d.ts +1 -1
  87. package/dist-types/index.d.ts +2 -2
  88. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  89. package/dist-types/server/init-cache-key.d.ts +1 -1
  90. package/dist-types/server/types.d.ts +1 -1
  91. package/dist-types/types/consent-manager.d.ts +1 -1
  92. package/dist-types/types/theme/style-keys.d.ts +1 -1
  93. package/dist-types/types/theme/style-types.d.ts +2 -2
  94. package/dist-types/utils/cn.d.ts +1 -1
  95. package/dist-types/utils/theme-utils.d.ts +1 -1
  96. package/dist-types/version.d.ts +1 -1
  97. package/docs/building-headless-components.md +40 -22
  98. package/docs/callbacks.md +76 -9
  99. package/docs/components/consent-banner.md +83 -9
  100. package/docs/components/consent-dialog.md +12 -2
  101. package/docs/components/consent-manager-provider.md +3 -1
  102. package/docs/components/consent-widget.md +61 -8
  103. package/docs/concepts/client-modes.md +16 -4
  104. package/docs/concepts/initialization-flow.md +9 -2
  105. package/docs/concepts/policy-packs.md +2 -2
  106. package/docs/hooks/use-consent-manager/overview.md +17 -3
  107. package/docs/hooks/use-ssr-status.md +1 -1
  108. package/docs/hooks/use-translations.md +1 -0
  109. package/docs/iab/consent-banner.md +2 -5
  110. package/docs/iab/consent-dialog.md +3 -6
  111. package/docs/iab/overview.md +11 -5
  112. package/docs/integrations/building-integrations.md +405 -0
  113. package/docs/integrations/databuddy.md +22 -5
  114. package/docs/integrations/google-tag-manager.md +2 -2
  115. package/docs/integrations/google-tag.md +2 -29
  116. package/docs/integrations/linkedin-insights.md +1 -1
  117. package/docs/integrations/meta-pixel.md +1 -1
  118. package/docs/integrations/microsoft-uet.md +1 -1
  119. package/docs/integrations/overview.md +18 -2
  120. package/docs/integrations/posthog.md +39 -17
  121. package/docs/integrations/tiktok-pixel.md +1 -1
  122. package/docs/integrations/x-pixel.md +1 -1
  123. package/docs/optimization.md +2 -6
  124. package/docs/policy-packs.md +7 -7
  125. package/docs/quickstart.md +11 -5
  126. package/docs/script-loader.md +22 -1
  127. package/docs/styling/tailwind.md +23 -17
  128. package/iab/styles.css +1 -0
  129. package/package.json +13 -11
  130. package/readme.json +6 -0
  131. package/src/iab/styles.css +11 -0
  132. package/src/iab/styles.tw3.css +8 -4
  133. package/src/styles.css +10 -0
  134. package/src/styles.tw3.css +7 -4
  135. package/styles.css +1 -0
  136. package/dist/components/shared/libs/policy-actions.cjs +0 -1
  137. package/dist/components/shared/libs/policy-actions.js +0 -1
  138. package/dist-types/components/shared/libs/policy-actions.d.ts +0 -26
@@ -27,7 +27,7 @@ linkedinInsights({
27
27
  |Property|Type|Description|Default|Required|
28
28
  |:--|:--|:--|:--|:--:|
29
29
  |id|string|Your LinkedIn Insights ID|-|✅ Required|
30
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'linkedin-insights'; \`src\`: \`https\://snap.licdn.com/li.lms-analytics/insight.min.js\`; \`category\`: 'marketing'|-|Optional|
30
+ |scriptSrc|string \|undefined|LinkedIn Insights loader URL.|-|Optional|
31
31
 
32
32
  ### Script
33
33
 
@@ -41,7 +41,7 @@ metaPixelEvent('Purchase', { value: 10.0, currency: 'USD' });
41
41
  |Property|Type|Description|Default|Required|
42
42
  |:--|:--|:--|:--|:--:|
43
43
  |pixelId|string|Your Meta Pixel ID|-|✅ Required|
44
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'meta-pixel'; \`src\`: \`https\://connect.facebook.net/en\_US/fbevents.js\`; \`category\`: 'marketing'|-|Optional|
44
+ |scriptSrc|string \|undefined|Meta Pixel loader URL.|-|Optional|
45
45
 
46
46
  ### Script
47
47
 
@@ -30,7 +30,7 @@ microsoftUet({
30
30
  |Property|Type|Description|Default|Required|
31
31
  |:--|:--|:--|:--|:--:|
32
32
  |id|string|Your Microsoft UET ID|-|✅ Required|
33
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'microsoft-uet'; \`src\`: \`//bat.bing.com/bat.js\`; \`category\`: 'marketing'|-|Optional|
33
+ |scriptSrc|string \|undefined|Microsoft UET loader URL.|-|Optional|
34
34
 
35
35
  ### Script
36
36
 
@@ -1,9 +1,16 @@
1
1
  ---
2
2
  title: Integrations
3
3
  description: "Many of your tools may require consent to be given before they can be used. This is especially true for analytics and marketing tools. \nc15t has various ways to integrate with your tools, depending on the tool you are using."
4
- lastModified: 2025-10-02
4
+ lastModified: 2026-04-10
5
5
 
6
6
  ---
7
+ c15t supports two integration styles:
8
+
9
+ * use a prebuilt helper from `@c15t/scripts`
10
+ * build your own script or manifest-backed helper
11
+
12
+ If you are building something reusable or contributing to `@c15t/scripts`, start with the [custom integration guide](/docs/integrations/building-integrations).
13
+
7
14
  ## General Pattern
8
15
 
9
16
  Every integration provides a script configuration function. Pass it to your framework's setup:
@@ -68,12 +75,21 @@ export function App({ children }: { children: React.ReactNode }) {
68
75
 
69
76
  Many marketing and analytics tools are commonly loaded using a script tag, such as Google Tag Manager (GTM), Google Tag (gtag.js), Meta Pixel and TikTok Pixel.
70
77
 
71
- c15t's script loader allows you to easily integrate your tools that require consent with c15t, to make this even easier we have provided a set of prebuilt scripts for you to use.
78
+ c15t's script loader allows you to easily integrate your tools that require consent with c15t. The prebuilt integrations in `@c15t/scripts` are the recommended starting point, and the custom integration guide explains how to build your own when you need something more specialized.
72
79
 
73
80
  * [JavaScript](/docs/frameworks/javascript/script-loader)
74
81
  * [React](/docs/frameworks/react/script-loader)
75
82
  * [Next.js](/docs/frameworks/next/script-loader)
76
83
 
84
+ ## Building Your Own
85
+
86
+ If you need a vendor we do not ship yet:
87
+
88
+ * build a one-off `Script` directly in your app for simple cases
89
+ * build a reusable manifest-backed helper for shared or package-level integrations
90
+
91
+ Read the [custom integration guide](/docs/integrations/building-integrations) for the manifest phases, structured step model, testing checklist, and devtools debugging flow.
92
+
77
93
  ## has() method
78
94
 
79
95
  The `has()` method allows you to check if the user has given consent for a specific purpose. You can learn more about the `has()` method [here](/docs/frameworks/javascript/store/checking-consent).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: PostHog
3
3
  description: PostHog is an open-source product analytics platform for tracking user behavior, session replays, feature flags, and A/B testing. It supports cookieless tracking, allowing analytics to continue even without cookie consent.
4
- lastModified: 2025-09-19
4
+ lastModified: 2026-04-08
5
5
 
6
6
  icon: posthog
7
7
  ---
@@ -25,7 +25,7 @@ This is the recommended approach if you're using the Posthog JS SDK, this is com
25
25
  posthog.opt_out_capturing() // Avoids accidental tracking without consent till c15t has loaded
26
26
  ```
27
27
 
28
- 2. **Adding the SDK callback to c15t** The PostHog SDK approach uses callbacks.onConsentSet instead of scripts. This lets you toggle PostHog's opt-in/opt-out based on the user's consent choice.
28
+ 2. **Sync settled consent once, then subscribe to real changes** The recommended PostHog SDK approach uses two phases: run one initial sync after c15t has finished resolving consent, then subscribe to future real preference changes with subscribeToConsentChanges().
29
29
 
30
30
  > ℹ️ Info:
31
31
  >
@@ -35,20 +35,38 @@ This is the recommended approach if you're using the Posthog JS SDK, this is com
35
35
  import { getOrCreateConsentRuntime } from 'c15t';
36
36
  import { posthog } from 'posthog-js';
37
37
 
38
- getOrCreateConsentRuntime({
38
+ function syncPostHogMeasurementConsent(hasMeasurementConsent: boolean) {
39
+ if (hasMeasurementConsent) {
40
+ posthog.opt_in_capturing();
41
+ } else {
42
+ posthog.opt_out_capturing();
43
+ }
44
+ }
45
+
46
+ const runtime = getOrCreateConsentRuntime({
39
47
  mode: 'hosted',
40
48
  callbacks: {
41
- onConsentSet({ preferences }) {
42
- if (preferences.measurement) {
43
- posthog.opt_in_capturing();
44
- } else {
45
- posthog.opt_out_capturing();
46
- }
47
- }
48
- }
49
+ onBannerFetched() {
50
+ syncPostHogMeasurementConsent(
51
+ runtime.consentStore.getState().has('measurement')
52
+ );
53
+ },
54
+ },
49
55
  });
56
+
57
+ runtime.consentStore
58
+ .getState()
59
+ .subscribeToConsentChanges(({ allowedCategories }) => {
60
+ syncPostHogMeasurementConsent(
61
+ allowedCategories.includes('measurement')
62
+ );
63
+ });
50
64
  ```
51
65
 
66
+ > ℹ️ Info:
67
+ >
68
+ > Avoid using onConsentSet plus manual deduplication for PostHog. subscribeToConsentChanges() already gives you the exact change-only semantics most analytics SDKs need.
69
+
52
70
  ## PostHog Script Implementation
53
71
 
54
72
  If you want to load posthog via a script tag it's recommended to use this approach.
@@ -80,8 +98,13 @@ By default c15t will always load the script regardless of consent. This is becau
80
98
  posthog({
81
99
  id: 'phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
82
100
  apiHost: 'https://eu.i.posthog.com',
83
- defaults: '2025-05-24',
84
- options: { person_profiles: 'identified_only' }
101
+ scriptUrl: 'https://eu-assets.i.posthog.com/static/array.js',
102
+ initOptions: {
103
+ api_host: 'https://eu.i.posthog.com',
104
+ ui_host: 'https://eu.i.posthog.com',
105
+ autocapture: false,
106
+ person_profiles: 'identified_only',
107
+ }
85
108
  })
86
109
  ```
87
110
 
@@ -92,10 +115,9 @@ By default c15t will always load the script regardless of consent. This is becau
92
115
  |Property|Type|Description|Default|Required|
93
116
  |:--|:--|:--|:--|:--:|
94
117
  |id|string|Your posthog id, begins with 'phc\_'.|-|✅ Required|
95
- |apiHost|string|Your posthog api host.|'https\://eu.i.posthog.com'|✅ Required|
96
- |defaults|string|The defaults for the posthog script.|-|✅ Required|
97
- |options|Record\<string, unknown>|Other optional options for the posthog script.|-|✅ Required|
98
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'posthog-consent'; \`category\`: 'measurement'|-|Optional|
118
+ |apiHost|string \|undefined|Your posthog api host.|'https\://eu.i.posthog.com'|Optional|
119
+ |scriptUrl|string \|undefined|The PostHog array loader URL.|-|Optional|
120
+ |initOptions|Record\<string, unknown> \|undefined|PostHog init options passed to \`posthog.init(...)\`.|-|Optional|
99
121
 
100
122
  ### Script
101
123
 
@@ -31,7 +31,7 @@ tiktokPixel({
31
31
  |Property|Type|Description|Default|Required|
32
32
  |:--|:--|:--|:--|:--:|
33
33
  |pixelId|string|Your TikTok Pixel ID|-|✅ Required|
34
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'tiktok-pixel'; \`src\`: \`https\://analytics.tiktok.com/i18n/pixel/events.js\`; \`category\`: 'marketing'|-|Optional|
34
+ |scriptSrc|string \|undefined|TikTok Pixel loader base URL.|-|Optional|
35
35
 
36
36
  ### Script
37
37
 
@@ -37,7 +37,7 @@ xPixelEvent('tw-xxxx-xxxx', { value: 10.00, currency: 'USD' });
37
37
  |Property|Type|Description|Default|Required|
38
38
  |:--|:--|:--|:--|:--:|
39
39
  |pixelId|string|Your X Pixel ID|-|✅ Required|
40
- |script|Script \|undefined|Override or extend the default script values. Options: \`id\`: 'x-pixel'; \`src\`: \`https\://static.ads-twitter.com/uwt.js\`; \`category\`: 'marketing'|-|Optional|
40
+ |scriptSrc|string \|undefined|X Pixel loader URL.|-|Optional|
41
41
 
42
42
  ### Script
43
43
 
@@ -50,7 +50,7 @@ const prefetchScript = buildPrefetchScript({
50
50
  </head>
51
51
  ```
52
52
 
53
- Then consume the prefetched data in your provider:
53
+ Then initialize your provider normally. Matching prefetched data is consumed automatically by the runtime during first store initialization:
54
54
 
55
55
  ```tsx
56
56
  import {
@@ -58,7 +58,6 @@ import {
58
58
  ConsentBanner,
59
59
  ConsentDialog,
60
60
  } from '@c15t/react';
61
- import { getPrefetchedInitialData } from 'c15t';
62
61
 
63
62
  export default function App({ children }: { children: React.ReactNode }) {
64
63
  return (
@@ -66,9 +65,6 @@ export default function App({ children }: { children: React.ReactNode }) {
66
65
  options={{
67
66
  mode: 'hosted',
68
67
  backendURL: '/api/c15t',
69
- ssrData: getPrefetchedInitialData({
70
- backendURL: '/api/c15t',
71
- }),
72
68
  }}
73
69
  >
74
70
  <ConsentBanner />
@@ -80,7 +76,7 @@ export default function App({ children }: { children: React.ReactNode }) {
80
76
  ```
81
77
 
82
78
  > ℹ️ **Info:**
83
- > If you use overrides or custom credentials in buildPrefetchScript, pass the same values to getPrefetchedInitialData(...) so it resolves the matching prefetched request.
79
+ > If overrides.gpc conflicts with the browser's ambient GPC signal, the prefetched entry is not reused and c15t falls back to a normal client /init.
84
80
 
85
81
  ## Keep The Provider Mounted Across Navigation
86
82
 
@@ -6,7 +6,7 @@ Policy packs configure how c15t handles regional consent — which model (opt-in
6
6
 
7
7
  **For most apps, you just need a `ConsentManagerProvider` pointing at your backend with presets configured there.** The frontend receives the resolved policy via the `/init` response — no client-side policy config required.
8
8
 
9
- When a backend isn't available — local development, static previews, Storybook, or as a resilience fallback — you can pass policies directly to the provider via `offlinePolicy.policyPacks` and c15t resolves them locally.
9
+ When a backend isn't available — local development, static previews, Storybook, automated tests, or as a resilience fallback during a temporary outage — you can pass policies directly to the provider via `offlinePolicy.policyPacks` and c15t resolves them locally.
10
10
 
11
11
  > ℹ️ **Info:**
12
12
  > For QA and testing, use the c15t DevTools to simulate different regions and policy responses against your real backend, rather than switching to offline mode.
@@ -29,9 +29,9 @@ When using consent.io or a self-hosted backend, the provider connects automatica
29
29
 
30
30
  The backend resolves the correct policy based on the visitor's geo data and returns it in the `/init` response. Configure your presets on the backend side.
31
31
 
32
- ## Offline Presets (Fallback)
32
+ ## Offline Presets (Development and Fallback)
33
33
 
34
- When no backend is available, pass presets directly to the provider:
34
+ Use offline presets mainly for local development, Storybook, deterministic tests, or temporary backend outages:
35
35
 
36
36
  ```tsx
37
37
  import { policyPackPresets } from '@c15t/react';
@@ -105,7 +105,7 @@ export function ConsentManager({ children }: { children: ReactNode }) {
105
105
 
106
106
  ## Offline / Fallback
107
107
 
108
- For local development, static sites, or when the backend is unreachable, pass policies directly:
108
+ For local development, previews, automated tests, or when the backend is temporarily unreachable, pass policies directly:
109
109
 
110
110
  ```tsx
111
111
  import type { ReactNode } from 'react';
@@ -144,7 +144,7 @@ export function ConsentManager({ children }: { children: ReactNode }) {
144
144
  ## Provider Shape
145
145
 
146
146
  Configure packs through `offlinePolicy.policyPacks`. Add `offlinePolicy.i18n`
147
- when you want offline mode to mirror hosted policy-profile language behavior:
147
+ when you want local previews or fallback behavior to mirror hosted policy-profile language behavior:
148
148
 
149
149
  ```tsx
150
150
  <ConsentManagerProvider
@@ -203,7 +203,7 @@ With that setup, offline mode resolves language the same way as hosted mode:
203
203
  |`offlinePolicy: { policyPacks: [] }`|Explicit no-banner mode|
204
204
  |Non-empty pack, no match, no default|Explicit no-banner mode|
205
205
 
206
- Omitting the option gives you a safe opt-in default for local development and outage scenarios. Providing it tells c15t you want policy-driven behavior exactly as configured.
206
+ Omitting the option gives you a safe opt-in default for local development and outage scenarios. Providing it tells c15t you want deterministic preview or fallback behavior exactly as configured.
207
207
 
208
208
  ## QA and Debugging
209
209
 
@@ -215,7 +215,7 @@ For deeper inspection:
215
215
  * Open the DevTools Policy panel to inspect matcher resolution and fingerprints
216
216
  * Compare your frontend preview with the backend `/init` response before shipping
217
217
 
218
- If you need fully deterministic resolution without a backend (e.g., in automated tests or Storybook), pair `offlinePolicy.policyPacks` with `overrides`:
218
+ If you need fully deterministic resolution without a backend during testing or preview work (for example, in automated tests or Storybook), pair `offlinePolicy.policyPacks` with `overrides`:
219
219
 
220
220
  ```tsx
221
221
  options={{
@@ -33,15 +33,17 @@ availableIn:
33
33
  |yarn|`yarn add @c15t/react`|
34
34
  |bun|`bun add @c15t/react`|
35
35
 
36
- 2. **Import styles** Import the prebuilt component stylesheet at the root of your application. This is required for styled components to render correctly.
36
+ 2. **Import styles** Import the prebuilt component stylesheet in your app-level CSS entrypoint. This is required for styled components to render correctly.
37
37
 
38
- ```tsx
39
- import '@c15t/react/styles.css';
38
+ ```css
39
+ @import "@c15t/react/styles.css";
40
40
  ```
41
41
 
42
+ Keeping the c15t stylesheet in your global CSS entrypoint makes layer and cascade order explicit. JS/TSX side-effect imports can load in a different order across framework and Tailwind tooling, which makes style regressions harder to debug.
43
+
42
44
  > ℹ️ Info:
43
45
  >
44
- > If you are using the headless API or fully custom styling, you can skip this import.
46
+ > If you are using the headless API or fully custom styling, you can skip this import. src/main.tsx should keep importing ./index.css as usual.
45
47
 
46
48
  3. **Create ConsentManager components** Create a provider component with the consent UI and a wrapper that re-exports it. This initializes the consent store and makes consent state available to all child components.
47
49
 
@@ -81,7 +83,11 @@ availableIn:
81
83
 
82
84
  > ℹ️ Info:
83
85
  >
84
- > Don't have a backend yet? You can use mode: 'offline' for local-only consent storage, but review the browser-only storage consequences before choosing it for production.
86
+ > Hosted mode is the recommended production setup because the backend resolves jurisdiction and policy, keeps durable consent records, and lets c15t recover from temporary network failures by re-syncing later.
87
+ >
88
+ > ℹ️ Info:
89
+ >
90
+ > Don't have a backend yet? You can use mode: 'offline' for local-only consent storage, but it gives up backend audit history, server-side consent awareness, and automatic jurisdiction detection. Review the browser-only storage consequences before choosing it for production.
85
91
 
86
92
  4. **Mount ConsentManager at the app root** Wrap your existing app tree with ConsentManager so all routes/components can access consent state.
87
93
 
@@ -4,7 +4,7 @@ description: Gate third-party scripts behind consent - load Google Analytics, Me
4
4
  ---
5
5
  The script loader manages third-party scripts based on consent state. Scripts are defined in the provider's `scripts` option and are automatically loaded when their required consent category is granted, and unloaded when consent is revoked.
6
6
 
7
- c15t has a collection of premade scripts available on the @c15t/scripts package. It's recomended to check if a pre-built integration exists before manually creating a script, see the [integrations overview](/docs/integrations/overview).
7
+ c15t has a collection of premade scripts available in `@c15t/scripts`. Check the [integrations overview](/docs/integrations/overview) first before manually building a script.
8
8
 
9
9
  |Package manager|Command|
10
10
  |:--|:--|
@@ -15,6 +15,12 @@ c15t has a collection of premade scripts available on the @c15t/scripts package.
15
15
 
16
16
  > ℹ️ **Info:**
17
17
  > We recommend using the pre-built integrations when possible.
18
+ >
19
+ > ℹ️ **Info:**
20
+ > If you need a vendor we do not ship yet, see the custom integration guide. It covers both one-off Script objects and reusable manifest-backed integrations.
21
+ >
22
+ > ℹ️ **Info:**
23
+ > For app-specific scripts, use a plain Script object. For reusable integrations, prefer a manifest-backed helper so startup phases, consent signaling, and future server-side loading support stay structured.
18
24
 
19
25
  ## Basic Usage
20
26
 
@@ -47,6 +53,21 @@ export function ConsentManager({ children }: { children: ReactNode }) {
47
53
  }
48
54
  ```
49
55
 
56
+ ## Choose the Right Approach
57
+
58
+ * Use a plain `Script` for one-off app code.
59
+ * Use a manifest-backed helper in `@c15t/scripts` for reusable integrations, contributions, or anything that needs structured startup behavior.
60
+
61
+ If you are building something reusable, start with the [custom integration guide](/docs/integrations/building-integrations) before using raw callbacks.
62
+
63
+ ## Reusable Integrations
64
+
65
+ For app-specific use, raw `Script` objects are usually enough.
66
+
67
+ For reusable integrations, c15t uses a manifest-backed model in `@c15t/scripts`. That keeps startup phases, consent signaling, and vendor-specific boot logic structured instead of hidden inside large callback bodies.
68
+
69
+ If you are building an integration for multiple apps or contributing upstream, use the [custom integration guide](/docs/integrations/building-integrations).
70
+
50
71
  ## Script Types
51
72
 
52
73
  ### Standard Scripts
@@ -6,41 +6,47 @@ c15t works with Tailwind CSS out of the box. Use the `slots` theme option to app
6
6
 
7
7
  ## Setup
8
8
 
9
- Import the standard c15t stylesheet once at the root of your app:
9
+ Import the standard c15t stylesheet once in your app-level CSS entrypoint:
10
10
 
11
- ```tsx
12
- // React
13
- import '@c15t/react/styles.css';
11
+ ```css
12
+ /* React: src/index.css */
13
+ @import "@c15t/react/styles.css";
14
14
 
15
- // Next.js
16
- import '@c15t/nextjs/styles.css';
15
+ /* Next.js: app/globals.css */
16
+ @import "@c15t/nextjs/styles.css";
17
17
  ```
18
18
 
19
+ Keeping the c15t stylesheet in your global CSS entrypoint makes layer and cascade order explicit. JS/TSX side-effect imports can load in a different order across framework and Tailwind tooling, which makes style regressions harder to debug.
20
+
19
21
  ### Tailwind v4
20
22
 
21
- Tailwind v4 automatically scans your source files. Import Tailwind normally. c15t component styles join Tailwind's `components` layer automatically, so no extra c15t-specific layer declaration is needed:
23
+ Tailwind v4 automatically scans your source files. Import Tailwind normally, then place the c15t stylesheet immediately after it. c15t component styles join Tailwind's `components` layer automatically, so no extra c15t-specific layer declaration is needed:
22
24
 
23
- ```css
25
+ ```css title="src/index.css"
24
26
  @import "tailwindcss";
27
+ @import "@c15t/react/styles.css";
25
28
  ```
26
29
 
27
- ### Tailwind v3
30
+ ```css title="app/globals.css"
31
+ @import "tailwindcss";
32
+ @import "@c15t/nextjs/styles.css";
33
+ ```
28
34
 
29
- Import the Tailwind 3-compatible c15t stylesheet before your app Tailwind globals, then keep your standard Tailwind directives in the app stylesheet:
35
+ ### Tailwind v3
30
36
 
31
- ```tsx title="app/layout.tsx"
32
- import '@c15t/react/styles.tw3.css';
33
- import './globals.css';
34
- ```
37
+ Import the Tailwind 3-compatible c15t stylesheet after `@tailwind components;` and before `@tailwind utilities;`:
35
38
 
36
- ```tsx title="app/layout.tsx"
37
- import '@c15t/nextjs/styles.tw3.css';
38
- import './globals.css';
39
+ ```css title="src/index.css"
40
+ @tailwind base;
41
+ @tailwind components;
42
+ @import "@c15t/react/styles.tw3.css";
43
+ @tailwind utilities;
39
44
  ```
40
45
 
41
46
  ```css title="app/globals.css"
42
47
  @tailwind base;
43
48
  @tailwind components;
49
+ @import "@c15t/nextjs/styles.tw3.css";
44
50
  @tailwind utilities;
45
51
  ```
46
52
 
package/iab/styles.css ADDED
@@ -0,0 +1 @@
1
+ @import "../dist/iab/styles.css";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/react",
3
- "version": "2.0.0-rc.7",
3
+ "version": "2.0.0-rc.9",
4
4
  "description": "Developer-first CMP for React: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization",
5
5
  "keywords": [
6
6
  "react",
@@ -32,16 +32,16 @@
32
32
  "type": "module",
33
33
  "exports": {
34
34
  "./styles.css": "./dist/styles.css",
35
- "./styles.tw3.css": "./src/styles.tw3.css",
35
+ "./styles.tw3.css": "./dist/styles.tw3.css",
36
36
  "./iab/styles.css": "./dist/iab/styles.css",
37
- "./iab/styles.tw3.css": "./src/iab/styles.tw3.css",
37
+ "./iab/styles.tw3.css": "./dist/iab/styles.tw3.css",
38
38
  "./primitives": {
39
- "types": "./dist/primitives.d.ts",
39
+ "types": "./dist-types/primitives.d.ts",
40
40
  "import": "./dist/primitives.js",
41
41
  "require": "./dist/primitives.cjs"
42
42
  },
43
43
  "./primitives/*": {
44
- "types": "./dist/primitives/*.d.ts",
44
+ "types": "./dist-types/primitives/*.d.ts",
45
45
  "import": "./dist/primitives/*.js",
46
46
  "require": "./dist/primitives/*.cjs"
47
47
  },
@@ -139,17 +139,19 @@
139
139
  "docs",
140
140
  "dist-types",
141
141
  "client",
142
+ "styles.css",
143
+ "iab",
142
144
  "src/styles.tw3.css",
143
145
  "src/iab/styles.tw3.css",
144
146
  "!dist/mockServiceWorker.js"
145
147
  ],
146
148
  "scripts": {
147
149
  "prebuild": "genversion --esm --semi src/version.ts",
148
- "build": "bun prebuild && rslib build && bun run clean:dist:test-artifacts && bun ../../scripts/agent-docs/generate-package-docs.ts @c15t/react",
150
+ "build": "bun prebuild && rslib build && bun ../../scripts/normalize-dist-types.mjs && bun scripts/generate-distribution-css.ts && bun run clean:dist:test-artifacts && bun ../../scripts/agent-docs/generate-package-docs.ts @c15t/react",
149
151
  "build:agent-docs": "bun ../../scripts/agent-docs/generate-package-docs.ts @c15t/react",
150
152
  "check-types": "bun prebuild && tsc --noEmit",
151
153
  "clean:dist:test-artifacts": "bun --eval \"import { rmSync } from 'node:fs'; rmSync('dist/mockServiceWorker.js', { force: true });\"",
152
- "dev": "bun prebuild && rslib build",
154
+ "dev": "bun prebuild && rslib build && bun ../../scripts/normalize-dist-types.mjs && bun scripts/generate-distribution-css.ts",
153
155
  "fmt": "bun biome format --write . && bun biome check --formatter-enabled=false --linter-enabled=false --write",
154
156
  "lint": "bun biome lint ./src",
155
157
  "prepack": "cd ../.. && bunx turbo run build --filter=@c15t/react",
@@ -162,13 +164,13 @@
162
164
  "not op_mini all"
163
165
  ],
164
166
  "dependencies": {
165
- "@c15t/ui": "2.0.0-rc.7",
166
- "c15t": "2.0.0-rc.6",
167
+ "@c15t/ui": "2.0.0-rc.9",
168
+ "c15t": "2.0.0-rc.8",
167
169
  "clsx": "2.1.1"
168
170
  },
169
171
  "devDependencies": {
170
- "@c15t/backend": "2.0.0-rc.6",
171
- "@c15t/iab": "2.0.0-rc.6",
172
+ "@c15t/backend": "2.0.0-rc.8",
173
+ "@c15t/iab": "2.0.0-rc.8",
172
174
  "@c15t/typescript-config": "0.0.1-beta.1",
173
175
  "@c15t/vitest-config": "1.0.0",
174
176
  "@iabtechlabtcf/core": "^1.5.20",
package/readme.json CHANGED
@@ -21,6 +21,12 @@
21
21
  "",
22
22
  "```bash\npnpm add @c15t/react\n```",
23
23
  "",
24
+ "Then add the prebuilt stylesheet to your app-level CSS entrypoint:",
25
+ "",
26
+ "```css\n/* src/index.css */\n@import \"@c15t/react/styles.css\";\n```",
27
+ "",
28
+ "If you use the prebuilt IAB TCF UI, also import `@c15t/react/iab/styles.css`. It is published separately so non-IAB apps do not ship those component rules.",
29
+ "",
24
30
  "To manually install, follow the guide in our [docs – manual setup](https://c15t.com/docs/frameworks/react/quickstart#manual-setup)."
25
31
  ],
26
32
  "usage": [
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @c15t/react — IAB TCF component styles.
3
+ *
4
+ * Add this stylesheet to the same global CSS entrypoint as your base c15t styles
5
+ * when using IAB consent components. Do not import either stylesheet from JS/TSX.
6
+ *
7
+ * Usage (src/index.css):
8
+ * @import "@c15t/react/styles.css";
9
+ * @import "@c15t/react/iab/styles.css";
10
+ */
11
+ @import "@c15t/ui/iab/styles.css";
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * @c15t/react/iab — Tailwind 3-compatible IAB component styles.
3
3
  *
4
- * Import this stylesheet before your app Tailwind globals so utility classes
5
- * can come after the c15t base rules.
4
+ * Add this stylesheet to the same global CSS entrypoint as your base c15t styles.
5
+ * Do not import either stylesheet from JS/TSX files.
6
6
  *
7
- * Usage:
8
- * import '@c15t/react/iab/styles.tw3.css';
7
+ * Usage (src/index.css):
8
+ * @tailwind base;
9
+ * @tailwind components;
10
+ * @import "@c15t/react/styles.tw3.css";
11
+ * @import "@c15t/react/iab/styles.tw3.css";
12
+ * @tailwind utilities;
9
13
  */
10
14
  @import "@c15t/ui/iab/styles.tw3.css";
package/src/styles.css ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @c15t/react — Non-IAB prebuilt component styles.
3
+ *
4
+ * Import this stylesheet once from your app-level CSS entrypoint
5
+ * when using prebuilt (styled) consent components.
6
+ *
7
+ * Usage (src/index.css):
8
+ * @import "@c15t/react/styles.css";
9
+ */
10
+ @import "@c15t/ui/styles.css";
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * @c15t/react — Tailwind 3-compatible prebuilt component styles.
3
3
  *
4
- * Import this stylesheet before your app Tailwind globals so utility classes
5
- * can come after the c15t base rules.
4
+ * Import this stylesheet in the same global CSS entrypoint as Tailwind 3,
5
+ * after `@tailwind components;` and before `@tailwind utilities;`.
6
6
  *
7
- * Usage:
8
- * import '@c15t/react/styles.tw3.css';
7
+ * Usage (src/index.css):
8
+ * @tailwind base;
9
+ * @tailwind components;
10
+ * @import "@c15t/react/styles.tw3.css";
11
+ * @tailwind utilities;
9
12
  */
10
13
  @import "@c15t/ui/styles.tw3.css";
package/styles.css ADDED
@@ -0,0 +1 @@
1
+ @import "./dist/styles.css";
@@ -1 +0,0 @@
1
- "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,o)=>{for(var r in o)__webpack_require__.o(o,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},__webpack_require__.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{flattenPolicyActionGroups:()=>flattenPolicyActionGroups,hasPolicyHints:()=>hasPolicyHints,resolvePolicyActionGroups:()=>resolvePolicyActionGroups,resolvePolicyAllowedActions:()=>resolvePolicyAllowedActions,resolvePolicyDirection:()=>resolvePolicyDirection,resolvePolicyOrderedActions:()=>resolvePolicyOrderedActions,resolvePolicyPrimaryActions:()=>resolvePolicyPrimaryActions,resolvePolicyUiProfile:()=>resolvePolicyUiProfile,shouldFillPolicyActions:()=>shouldFillPolicyActions});const DEFAULT_POLICY_ACTIONS=["reject","accept","customize"];function dedupeActions(e){return e&&0!==e.length?[...new Set(e)]:[]}function resolvePolicyAllowedActions(e){let o=dedupeActions(e.allowedActions);return o.length>0?o:[...DEFAULT_POLICY_ACTIONS]}function flattenPolicyActionGroups(e){return e&&0!==e.length?e.flatMap(e=>Array.isArray(e)?e:[e]):[]}function resolvePolicyActionGroups(e){let o=dedupeActions(e.allowedActions);if(0===o.length)return[];if(!e.layout||0===e.layout.length)return[[...o]];let r=new Set(o),i=[],t=new Set;for(let o of e.layout){let e=dedupeActions(Array.isArray(o)?o:[o]).filter(e=>!(!r.has(e)||t.has(e))&&(t.add(e),!0));e.length>0&&i.push(e)}return i.length>0?i:[[...o]]}function resolvePolicyOrderedActions(e){return flattenPolicyActionGroups(resolvePolicyActionGroups({allowedActions:e.allowedActions,layout:e.layout}))}function resolvePolicyPrimaryActions(e){let o=e.orderedActions.includes("customize")?["customize"]:[];if(!e.primaryActions||0===e.primaryActions.length)return o;let r=e.primaryActions.filter(o=>e.orderedActions.includes(o));return r.length>0?r:o}function resolvePolicyDirection(e){return"column"===e?"column":"row"}function resolvePolicyUiProfile(e){return"balanced"===e||"compact"===e||"strict"===e?e:"compact"}function shouldFillPolicyActions(e){let o=resolvePolicyUiProfile(e.uiProfile),r=new Set(e.actionGroups.flat()).size,i=e.actionGroups.length>1,t="column"===e.direction;return"strict"===o||"balanced"===o&&(r<=2||3===r&&(i||t))}function hasPolicyHints(e){return!!e&&Object.values(e).some(e=>Array.isArray(e)?e.length>0:void 0!==e)}for(var __rspack_i in exports.flattenPolicyActionGroups=__webpack_exports__.flattenPolicyActionGroups,exports.hasPolicyHints=__webpack_exports__.hasPolicyHints,exports.resolvePolicyActionGroups=__webpack_exports__.resolvePolicyActionGroups,exports.resolvePolicyAllowedActions=__webpack_exports__.resolvePolicyAllowedActions,exports.resolvePolicyDirection=__webpack_exports__.resolvePolicyDirection,exports.resolvePolicyOrderedActions=__webpack_exports__.resolvePolicyOrderedActions,exports.resolvePolicyPrimaryActions=__webpack_exports__.resolvePolicyPrimaryActions,exports.resolvePolicyUiProfile=__webpack_exports__.resolvePolicyUiProfile,exports.shouldFillPolicyActions=__webpack_exports__.shouldFillPolicyActions,__webpack_exports__)-1===["flattenPolicyActionGroups","hasPolicyHints","resolvePolicyActionGroups","resolvePolicyAllowedActions","resolvePolicyDirection","resolvePolicyOrderedActions","resolvePolicyPrimaryActions","resolvePolicyUiProfile","shouldFillPolicyActions"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
@@ -1 +0,0 @@
1
- let t=["reject","accept","customize"];function e(t){return t&&0!==t.length?[...new Set(t)]:[]}function o(o){let n=e(o.allowedActions);return n.length>0?n:[...t]}function n(t){return t&&0!==t.length?t.flatMap(t=>Array.isArray(t)?t:[t]):[]}function r(t){let o=e(t.allowedActions);if(0===o.length)return[];if(!t.layout||0===t.layout.length)return[[...o]];let n=new Set(o),r=[],l=new Set;for(let o of t.layout){let t=e(Array.isArray(o)?o:[o]).filter(t=>!(!n.has(t)||l.has(t))&&(l.add(t),!0));t.length>0&&r.push(t)}return r.length>0?r:[[...o]]}function l(t){return n(r({allowedActions:t.allowedActions,layout:t.layout}))}function i(t){let e=t.orderedActions.includes("customize")?["customize"]:[];if(!t.primaryActions||0===t.primaryActions.length)return e;let o=t.primaryActions.filter(e=>t.orderedActions.includes(e));return o.length>0?o:e}function c(t){return"column"===t?"column":"row"}function u(t){return"balanced"===t||"compact"===t||"strict"===t?t:"compact"}function s(t){let e=u(t.uiProfile),o=new Set(t.actionGroups.flat()).size,n=t.actionGroups.length>1,r="column"===t.direction;return"strict"===e||"balanced"===e&&(o<=2||3===o&&(n||r))}function a(t){return!!t&&Object.values(t).some(t=>Array.isArray(t)?t.length>0:void 0!==t)}export{n as flattenPolicyActionGroups,a as hasPolicyHints,r as resolvePolicyActionGroups,o as resolvePolicyAllowedActions,c as resolvePolicyDirection,l as resolvePolicyOrderedActions,i as resolvePolicyPrimaryActions,u as resolvePolicyUiProfile,s as shouldFillPolicyActions};
@@ -1,26 +0,0 @@
1
- import type { PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig } from '../../../../../core/dist-types/index.d.ts';
2
- export type { PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig, };
3
- export declare function resolvePolicyAllowedActions(params: {
4
- allowedActions?: PolicyUiAction[];
5
- }): PolicyUiAction[];
6
- export declare function flattenPolicyActionGroups(layout?: PolicyUiActionGroup[]): PolicyUiAction[];
7
- export declare function resolvePolicyActionGroups(params: {
8
- allowedActions: PolicyUiAction[];
9
- layout?: PolicyUiActionGroup[];
10
- }): PolicyUiAction[][];
11
- export declare function resolvePolicyOrderedActions(params: {
12
- allowedActions: PolicyUiAction[];
13
- layout?: PolicyUiActionGroup[];
14
- }): PolicyUiAction[];
15
- export declare function resolvePolicyPrimaryActions(params: {
16
- orderedActions: PolicyUiAction[];
17
- primaryActions?: PolicyUiAction[];
18
- }): PolicyUiAction[];
19
- export declare function resolvePolicyDirection(direction?: PolicyUiActionDirection): PolicyUiActionDirection;
20
- export declare function resolvePolicyUiProfile(profile?: PolicyUiProfile): PolicyUiProfile;
21
- export declare function shouldFillPolicyActions(params: {
22
- uiProfile?: PolicyUiProfile;
23
- actionGroups: PolicyUiAction[][];
24
- direction?: PolicyUiActionDirection;
25
- }): boolean;
26
- export declare function hasPolicyHints(surface?: PolicyUiSurfaceConfig): boolean;