@c15t/react 2.0.0-rc.6 → 2.0.0-rc.8

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 (168) hide show
  1. package/README.md +9 -0
  2. package/dist/components/consent-banner/atoms/root.cjs +1 -1
  3. package/dist/components/consent-banner/atoms/root.js +1 -1
  4. package/dist/components/consent-banner/consent-banner.cjs +1 -1
  5. package/dist/components/consent-banner/consent-banner.js +1 -1
  6. package/dist/components/consent-banner/index.cjs +1 -1
  7. package/dist/components/consent-banner/index.js +1 -1
  8. package/dist/components/consent-banner/policy-actions.cjs +1 -0
  9. package/dist/components/consent-banner/policy-actions.js +1 -0
  10. package/dist/components/consent-dialog/atoms/card.cjs +1 -1
  11. package/dist/components/consent-dialog/atoms/card.js +1 -1
  12. package/dist/components/consent-dialog/atoms/root.cjs +1 -1
  13. package/dist/components/consent-dialog/atoms/root.js +1 -1
  14. package/dist/components/consent-dialog-trigger/atoms/icon.cjs +1 -1
  15. package/dist/components/consent-dialog-trigger/atoms/icon.js +1 -1
  16. package/dist/components/consent-widget/atoms/accordion.cjs +1 -1
  17. package/dist/components/consent-widget/atoms/accordion.js +1 -1
  18. package/dist/components/consent-widget/atoms/footer.cjs +1 -1
  19. package/dist/components/consent-widget/atoms/footer.js +1 -1
  20. package/dist/components/consent-widget/consent-widget.cjs +1 -1
  21. package/dist/components/consent-widget/consent-widget.js +1 -1
  22. package/dist/components/consent-widget/index.cjs +1 -1
  23. package/dist/components/consent-widget/index.js +1 -1
  24. package/dist/components/consent-widget/policy-actions.cjs +1 -0
  25. package/dist/components/consent-widget/policy-actions.js +1 -0
  26. package/dist/components/iab-consent-banner/atoms/card.cjs +1 -1
  27. package/dist/components/iab-consent-banner/atoms/card.js +1 -1
  28. package/dist/components/iab-consent-banner/atoms/footer.cjs +1 -1
  29. package/dist/components/iab-consent-banner/atoms/footer.js +1 -1
  30. package/dist/components/iab-consent-banner/atoms/header.cjs +1 -1
  31. package/dist/components/iab-consent-banner/atoms/header.js +1 -1
  32. package/dist/components/iab-consent-banner/atoms/root.cjs +1 -1
  33. package/dist/components/iab-consent-banner/atoms/root.js +1 -1
  34. package/dist/components/iab-consent-banner/iab-consent-banner.cjs +1 -1
  35. package/dist/components/iab-consent-banner/iab-consent-banner.js +1 -1
  36. package/dist/components/iab-consent-dialog/atoms/card.cjs +1 -1
  37. package/dist/components/iab-consent-dialog/atoms/card.js +1 -1
  38. package/dist/components/iab-consent-dialog/atoms/footer.cjs +1 -1
  39. package/dist/components/iab-consent-dialog/atoms/footer.js +1 -1
  40. package/dist/components/iab-consent-dialog/atoms/header.cjs +1 -1
  41. package/dist/components/iab-consent-dialog/atoms/header.js +1 -1
  42. package/dist/components/iab-consent-dialog/atoms/root.cjs +1 -1
  43. package/dist/components/iab-consent-dialog/atoms/root.js +1 -1
  44. package/dist/components/iab-consent-dialog/iab-consent-dialog.cjs +1 -1
  45. package/dist/components/iab-consent-dialog/iab-consent-dialog.js +1 -1
  46. package/dist/components/shared/policy-actions.cjs +1 -0
  47. package/dist/components/shared/policy-actions.js +1 -0
  48. package/dist/components/shared/primitives/box.cjs +1 -1
  49. package/dist/components/shared/primitives/box.js +1 -1
  50. package/dist/components/shared/ui/branding.cjs +1 -0
  51. package/dist/components/shared/ui/branding.js +1 -0
  52. package/dist/components/shared/ui/logo.cjs +1 -1
  53. package/dist/components/shared/ui/logo.js +1 -1
  54. package/dist/components/shared/ui/switch/index.cjs +1 -1
  55. package/dist/components/shared/ui/switch/index.js +1 -1
  56. package/dist/hooks/index.cjs +1 -1
  57. package/dist/hooks/use-consent-manager.cjs +1 -1
  58. package/dist/hooks/use-consent-manager.js +1 -1
  59. package/dist/hooks/use-headless-consent-ui.cjs +1 -1
  60. package/dist/hooks/use-headless-consent-ui.js +1 -1
  61. package/dist/iab/styles.css +11 -1
  62. package/dist/iab/styles.tw3.css +14 -0
  63. package/dist/providers/consent-manager-provider.cjs +1 -1
  64. package/dist/providers/consent-manager-provider.js +1 -1
  65. package/dist/server/fetch-ssr-data.cjs +1 -1
  66. package/dist/server/fetch-ssr-data.js +1 -1
  67. package/dist/styles.css +10 -1
  68. package/dist/styles.tw3.css +13 -0
  69. package/dist/version.cjs +1 -1
  70. package/dist/version.js +1 -1
  71. package/dist-types/components/consent-banner/atoms/root.d.ts +44 -13
  72. package/dist-types/components/consent-banner/consent-banner.d.ts +7 -2
  73. package/dist-types/components/consent-banner/index.d.ts +47 -24
  74. package/dist-types/components/consent-banner/policy-actions.d.ts +11 -0
  75. package/dist-types/components/consent-dialog/atoms/card.d.ts +7 -2
  76. package/dist-types/components/consent-dialog/atoms/root.d.ts +1 -1
  77. package/dist-types/components/consent-dialog/consent-dialog.d.ts +1 -1
  78. package/dist-types/components/consent-dialog/index.d.ts +29 -8
  79. package/dist-types/components/consent-dialog-trigger/atoms/icon.d.ts +1 -1
  80. package/dist-types/components/consent-dialog-trigger/types.d.ts +1 -1
  81. package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +0 -1
  82. package/dist-types/components/consent-widget/atoms/accordion.d.ts +4 -3
  83. package/dist-types/components/consent-widget/index.d.ts +26 -6
  84. package/dist-types/components/consent-widget/policy-actions.d.ts +11 -0
  85. package/dist-types/components/frame/atoms.d.ts +1 -1
  86. package/dist-types/components/frame/types.d.ts +1 -1
  87. package/dist-types/components/iab-consent-banner/atoms/root.d.ts +1 -1
  88. package/dist-types/components/iab-consent-banner/iab-consent-banner.d.ts +1 -1
  89. package/dist-types/components/iab-consent-dialog/atoms/root.d.ts +1 -1
  90. package/dist-types/components/iab-consent-dialog/atoms/vendor-list.d.ts +1 -1
  91. package/dist-types/components/iab-consent-dialog/iab-consent-dialog.d.ts +1 -1
  92. package/dist-types/components/shared/policy-actions.d.ts +29 -0
  93. package/dist-types/components/shared/primitives/button.d.ts +1 -1
  94. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  95. package/dist-types/components/shared/primitives/legal-links/index.d.ts +1 -1
  96. package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
  97. package/dist-types/components/shared/ui/accordion/accordion.types.d.ts +1 -1
  98. package/dist-types/components/shared/ui/branding.d.ts +23 -0
  99. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  100. package/dist-types/components/shared/ui/logo.d.ts +8 -0
  101. package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +1 -1
  102. package/dist-types/components/shared/ui/switch/index.d.ts +1 -1
  103. package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
  104. package/dist-types/components/shared/ui/switch/switch.types.d.ts +1 -1
  105. package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -1
  106. package/dist-types/context/consent-manager-context.d.ts +1 -1
  107. package/dist-types/headless.d.ts +1 -1
  108. package/dist-types/hooks/use-component-config.d.ts +1 -1
  109. package/dist-types/hooks/use-consent-manager.d.ts +1 -1
  110. package/dist-types/hooks/use-headless-consent-ui.d.ts +1 -1
  111. package/dist-types/hooks/use-ssr-status.d.ts +2 -1
  112. package/dist-types/hooks/use-translations.d.ts +1 -1
  113. package/dist-types/index.d.ts +2 -2
  114. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  115. package/dist-types/server/init-cache-key.d.ts +1 -1
  116. package/dist-types/server/types.d.ts +1 -1
  117. package/dist-types/types/consent-manager.d.ts +1 -1
  118. package/dist-types/types/theme/style-keys.d.ts +1 -1
  119. package/dist-types/types/theme/style-types.d.ts +2 -2
  120. package/dist-types/utils/cn.d.ts +1 -1
  121. package/dist-types/utils/theme-utils.d.ts +1 -1
  122. package/dist-types/version.d.ts +1 -1
  123. package/docs/building-headless-components.md +43 -22
  124. package/docs/callbacks.md +76 -9
  125. package/docs/components/consent-banner.md +148 -24
  126. package/docs/components/consent-dialog.md +42 -3
  127. package/docs/components/consent-manager-provider.md +3 -1
  128. package/docs/components/consent-widget.md +91 -9
  129. package/docs/concepts/client-modes.md +16 -4
  130. package/docs/concepts/initialization-flow.md +9 -2
  131. package/docs/concepts/policy-packs.md +2 -2
  132. package/docs/headless.md +13 -7
  133. package/docs/hooks/use-consent-manager/overview.md +17 -3
  134. package/docs/hooks/use-ssr-status.md +1 -1
  135. package/docs/hooks/use-translations.md +1 -0
  136. package/docs/iab/consent-banner.md +2 -5
  137. package/docs/iab/consent-dialog.md +3 -6
  138. package/docs/iab/overview.md +11 -5
  139. package/docs/integrations/building-integrations.md +405 -0
  140. package/docs/integrations/databuddy.md +22 -5
  141. package/docs/integrations/google-tag-manager.md +2 -2
  142. package/docs/integrations/google-tag.md +2 -29
  143. package/docs/integrations/linkedin-insights.md +1 -1
  144. package/docs/integrations/meta-pixel.md +1 -1
  145. package/docs/integrations/microsoft-uet.md +1 -1
  146. package/docs/integrations/overview.md +18 -2
  147. package/docs/integrations/posthog.md +39 -17
  148. package/docs/integrations/tiktok-pixel.md +1 -1
  149. package/docs/integrations/x-pixel.md +1 -1
  150. package/docs/optimization.md +2 -6
  151. package/docs/policy-packs.md +7 -7
  152. package/docs/quickstart.md +11 -5
  153. package/docs/script-loader.md +22 -1
  154. package/docs/styling/classnames.md +17 -9
  155. package/docs/styling/overview.md +166 -29
  156. package/docs/styling/slots.md +37 -7
  157. package/docs/styling/tailwind.md +25 -27
  158. package/iab/styles.css +1 -0
  159. package/package.json +15 -9
  160. package/readme.json +6 -0
  161. package/src/iab/styles.css +11 -0
  162. package/src/iab/styles.tw3.css +14 -0
  163. package/src/styles.css +10 -0
  164. package/src/styles.tw3.css +13 -0
  165. package/styles.css +1 -0
  166. package/dist/components/shared/libs/policy-actions.cjs +0 -1
  167. package/dist/components/shared/libs/policy-actions.js +0 -1
  168. package/dist-types/components/shared/libs/policy-actions.d.ts +0 -26
@@ -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
@@ -2,15 +2,11 @@
2
2
  title: Class Names
3
3
  description: Style consent components using className props and per-slot className targeting via the theme.
4
4
  ---
5
- ## Component className
5
+ ## Prefer Slots for Stock Components
6
6
 
7
- All consent components accept a `className` prop:
7
+ There is no single top-level `className` contract across every pre-built consent component.
8
8
 
9
- ```tsx
10
- <ConsentBanner className="my-banner" />
11
- <ConsentDialog className="my-dialog" />
12
- <ConsentWidget className="my-widget" />
13
- ```
9
+ For the stock `ConsentBanner`, `ConsentDialog`, and `ConsentWidget`, prefer `theme.slots` first. That keeps the markup intact and lets you target the exact part you need.
14
10
 
15
11
  ## Per-Slot className
16
12
 
@@ -59,9 +55,19 @@ const theme = {
59
55
  }
60
56
  ```
61
57
 
62
- ## noStyle Mode
58
+ ## When to Use Raw className
59
+
60
+ Use raw className-level styling when:
61
+
62
+ * your styling system is already class-driven
63
+ * tokens are too broad for the change
64
+ * slots already identify the correct element
63
65
 
64
- Use `noStyle` when you want to remove defaults and style from scratch:
66
+ If the request is "make the banner footer darker", prefer `theme.colors.surfaceHover` first. If the request is "add a border and spacing only to the footer", prefer `theme.slots.consentBannerFooter`.
67
+
68
+ ## Advanced: `noStyle`
69
+
70
+ Use `noStyle` only when you want to remove defaults and style from scratch while still keeping c15t's component structure:
65
71
 
66
72
  ```tsx
67
73
  {/* Remove all styles from a specific component */}
@@ -81,4 +87,6 @@ const theme = {
81
87
  } satisfies Theme;
82
88
  ```
83
89
 
90
+ Treat `noStyle` as an advanced escape hatch. Do not jump to it just because a token or slot needs debugging.
91
+
84
92
  For full custom markup and behavior, continue to [Headless Mode](../headless).
@@ -2,24 +2,34 @@
2
2
  title: Styling Overview
3
3
  description: Customize every aspect of c15t's consent components using design tokens, component slots, and CSS variables.
4
4
  ---
5
- c15t's theming system gives you multiple levels of control, from high-level design tokens to complete style removal.
5
+ c15t's theming system gives you multiple levels of control, but most customization should stay inside the pre-built components.
6
6
 
7
- The flow:
7
+ Start with the lowest-power tool that solves the problem:
8
8
 
9
- 1. **Define** tokens (colors, spacing, radius, etc.) in JavaScript
10
- 2. Tokens are **injected** as CSS custom properties (`--c15t-*`) at runtime
11
- 3. Components **consume** these variables in their default styles
12
- 4. You **override** at any level: tokens, slots, CSS variables, or raw classNames
9
+ 1. **Pre-built component APIs** provider options and component props such as `layout`, `direction`, `primaryButton`, `legalLinks`, and `theme.consentActions`
10
+ 2. **Design tokens** global colors, typography, spacing, radius, shadows, and motion
11
+ 3. **Slots** targeted styling for specific parts such as the banner card, footer, or title
12
+ 4. **CSS variables or className-level overrides** when you need to integrate with external CSS systems
13
+ 5. **Compound components** — when you must rearrange markup while still using c15t primitives
14
+ 6. **`noStyle`** — when you want c15t structure but you need to own all visual styling
15
+ 7. **Headless** — when you want fully custom markup and behavior
16
+
17
+ Keep styling and escalation as separate decisions:
18
+
19
+ * If you are still using the stock banner, dialog, or widget, stay with props, tokens, and slots.
20
+ * Escalate to compound components, `noStyle`, or headless only when the structure or behavior itself must change.
13
21
 
14
22
  ## Styling Approaches
15
23
 
16
24
  |Approach|Control|Use When|
17
25
  |--|--|--|
26
+ |**Component and provider APIs**|High|Reordering actions, changing button emphasis, configuring links, hiding branding, changing copy via `i18n`|
18
27
  |**Tokens**|High|Changing global colors, typography, spacing, radius, shadows, or motion|
19
- |**Slots**|Medium|Targeting specific component parts (e.g., the banner title, dialog footer)|
20
- |**CSS Variables**|Medium|Overriding `--c15t-*` variables from external CSS|
21
- |**className**|Medium|Passing class names to components or slots|
22
- |**noStyle**|Full|Removing all default styles, building from scratch|
28
+ |**Slots**|Medium|Targeting specific component parts (for example `consentBannerFooter` or `consentDialogCard`)|
29
+ |**CSS variables / className**|Medium|Integrating with an existing stylesheet or utility classes after tokens and slots|
30
+ |**Compound components**|Structure|Rearranging existing c15t primitives without going fully custom|
31
+ |**noStyle**|Full visuals|Keeping c15t structure but replacing all visual defaults|
32
+ |**Headless**|Full|Replacing both markup and behavior|
23
33
 
24
34
  ## Quick Start
25
35
 
@@ -61,9 +71,25 @@ export function ConsentManager({ children }: { children: ReactNode }) {
61
71
  }
62
72
  ```
63
73
 
64
- ## Styling Paths
74
+ ## Styling Inside Pre-Built Components
75
+
76
+ Start here before you consider compound components or headless mode.
77
+
78
+ ### 1. Provider and component configuration
79
+
80
+ Use the stock APIs first:
81
+
82
+ * `layout`, `direction`, and `primaryButton` for banner action arrangement
83
+ * `legalLinks` for link visibility
84
+ * `hideBranding` and `showTrigger` for dialog and widget behavior
85
+ * `theme.consentActions` for stock banner and dialog button treatment
86
+ * `i18n` on `ConsentManagerProvider` for copy changes
87
+
88
+ ```tsx
89
+ <ConsentBanner layout={['customize', ['reject', 'accept']]} primaryButton="accept" />
90
+ ```
65
91
 
66
- ### 1. Design tokens
92
+ ### 2. Design tokens
67
93
 
68
94
  Set global values for colors, typography, spacing, radius, shadows, and motion:
69
95
 
@@ -71,55 +97,140 @@ Set global values for colors, typography, spacing, radius, shadows, and motion:
71
97
  options={{ theme: { colors: { primary: '#6366f1' } } }}
72
98
  ```
73
99
 
74
- ### 2. Component slots
100
+ Use tokens first when the change is semantic:
101
+
102
+ * Banner card background -> `theme.colors.surface`
103
+ * Banner footer background -> `theme.colors.surfaceHover`
104
+ * Shared copy color -> `theme.colors.text` and `theme.colors.textMuted`
105
+
106
+ ```tsx
107
+ options={{
108
+ theme: {
109
+ colors: {
110
+ surface: '#ffffff',
111
+ surfaceHover: '#f6f3ee',
112
+ },
113
+ },
114
+ }}
115
+ ```
116
+
117
+ ### 3. Component slots
75
118
 
76
119
  Target specific component parts via the `slots` object:
77
120
 
78
121
  ```tsx
79
- options={{ theme: { slots: { consentBannerTitle: 'text-2xl font-bold' } } }}
122
+ options={{
123
+ theme: {
124
+ slots: {
125
+ consentBannerCard: 'rounded-[28px] shadow-xl',
126
+ consentBannerFooter: 'border-t border-black/10',
127
+ consentBannerTitle: 'tracking-tight',
128
+ },
129
+ },
130
+ }}
80
131
  ```
81
132
 
82
- ### 3. CSS variables
133
+ Use slots when the component part is right but the local styling needs adjustment.
83
134
 
84
- Override `--c15t-*` custom properties in your stylesheet.
135
+ ### 4. CSS variables and className-level overrides
85
136
 
86
- ### 4. className prop
137
+ Override `--c15t-*` custom properties in your stylesheet or attach classes through slots when your app styling is driven externally.
87
138
 
88
- Pass className directly to components:
139
+ Reach for this after tokens and slots, not before.
89
140
 
90
141
  ```tsx
91
- <ConsentBanner className="my-custom-banner" />
142
+ options={{
143
+ theme: {
144
+ slots: {
145
+ consentBannerFooter: 'bg-[var(--banner-footer)]',
146
+ },
147
+ },
148
+ }}
92
149
  ```
93
150
 
94
- ### 5. noStyle prop
151
+ ## Escalating Beyond Pre-Built Components
152
+
153
+ Only move up this ladder when the lower rung cannot satisfy the request.
95
154
 
96
- Strip all default styles and build from scratch (best paired with [Headless Mode](../headless)):
155
+ ### 5. Compound components
156
+
157
+ Use compound components when you need to rearrange existing c15t primitives:
158
+
159
+ ```tsx
160
+ <ConsentBanner.Root>
161
+ <ConsentBanner.Card>
162
+ <ConsentBanner.Header>
163
+ <ConsentBanner.Title />
164
+ <ConsentBanner.Description />
165
+ </ConsentBanner.Header>
166
+ <ConsentBanner.Footer>
167
+ <ConsentBanner.CustomizeButton />
168
+ <ConsentBanner.FooterSubGroup>
169
+ <ConsentBanner.RejectButton />
170
+ <ConsentBanner.AcceptButton />
171
+ </ConsentBanner.FooterSubGroup>
172
+ </ConsentBanner.Footer>
173
+ </ConsentBanner.Card>
174
+ </ConsentBanner.Root>
175
+ ```
176
+
177
+ ### 6. `noStyle`
178
+
179
+ Use `noStyle` only when the c15t structure is still correct but you want to replace all visual defaults:
97
180
 
98
181
  ```tsx
99
182
  <ConsentBanner noStyle />
100
183
  ```
101
184
 
185
+ ### 7. Headless
186
+
187
+ Go headless only when you are replacing both markup and behavior. For that path, continue to [Headless Mode](../headless).
188
+
102
189
  ## Common Styling Tasks
103
190
 
104
- ### Change brand color globally
191
+ ### Change the banner footer background
105
192
 
106
193
  ```tsx
107
- options={{ theme: { colors: { primary: '#0ea5e9', primaryHover: '#0284c7' } } }}
194
+ options={{
195
+ theme: {
196
+ colors: {
197
+ surfaceHover: '#f6f3ee',
198
+ },
199
+ },
200
+ }}
108
201
  ```
109
202
 
110
- ### Make the banner card more compact
203
+ Use `theme.colors.surfaceHover` before trying raw CSS.
204
+
205
+ ### Change the banner card background
111
206
 
112
207
  ```tsx
113
- options={{ theme: { spacing: { md: '0.75rem', lg: '1rem' } } }}
208
+ options={{
209
+ theme: {
210
+ colors: {
211
+ surface: '#fffdf8',
212
+ },
213
+ },
214
+ }}
114
215
  ```
115
216
 
116
- ### Round primary/secondary buttons
217
+ Use `theme.colors.surface` before overriding banner CSS variables directly.
218
+
219
+ ### Tweak the banner card, footer, or title styling without changing markup
117
220
 
118
221
  ```tsx
119
- options={{ theme: { slots: { buttonPrimary: 'rounded-full', buttonSecondary: 'rounded-full' } } }}
222
+ options={{
223
+ theme: {
224
+ slots: {
225
+ consentBannerCard: 'rounded-[28px] shadow-xl',
226
+ consentBannerFooter: 'border-t border-black/10 px-6',
227
+ consentBannerTitle: 'text-xl tracking-tight',
228
+ },
229
+ },
230
+ }}
120
231
  ```
121
232
 
122
- ### Change consent action button styles semantically
233
+ ### Change stock consent action button styles semantically
123
234
 
124
235
  ```tsx
125
236
  options={{
@@ -135,6 +246,29 @@ options={{
135
246
 
136
247
  Use `theme.consentActions` when you want to change the stock banner/dialog button treatment without rewriting the component layout. Policy packs still control action arrangement and primary-action hints. The theme controls whether those actions render as `stroke`, `filled`, `ghost`, or `lighter`.
137
248
 
249
+ ### Change banner copy without replacing the component
250
+
251
+ ```tsx
252
+ options={{
253
+ i18n: {
254
+ locale: 'en',
255
+ messages: {
256
+ en: {
257
+ cookieBanner: {
258
+ title: 'We value your privacy',
259
+ description: 'We use cookies to improve the site and measure performance.',
260
+ },
261
+ common: {
262
+ acceptAll: 'Accept all',
263
+ rejectAll: 'Reject all',
264
+ customize: 'Manage preferences',
265
+ },
266
+ },
267
+ },
268
+ },
269
+ }}
270
+ ```
271
+
138
272
  ### Enable dark mode safely
139
273
 
140
274
  ```tsx
@@ -147,8 +281,11 @@ options={{
147
281
  }}
148
282
  ```
149
283
 
284
+ > ℹ️ **Info:**
285
+ > If a token change does not show up where you expect, check how that component maps tokens to CSS variables before escalating. For example, the stock banner footer background comes from colors.surfaceHover, not a separate footer token.
286
+ >
150
287
  > ⚠️ **Warning:**
151
- > noStyle: true removes layout and visual defaults. Use it only when you want full control.Use either tokens/slots or raw CSS variable overrides intentionally to avoid conflicting style sources.For dark mode, c15t supports .dark and .c15t-dark.
288
+ > Do not jump to CSS overrides or !important because a token did not appear to work at first glance.noStyle: true removes layout and visual defaults. Treat it as an advanced opt-out, not a normal theming step.Headless mode is for replacing markup and behavior, not for styling-only requests.Use either tokens/slots or raw CSS variable overrides intentionally to avoid conflicting style sources.For dark mode, c15t supports .dark and .c15t-dark.
152
289
 
153
290
  ## API Reference
154
291