@c15t/nextjs 2.0.0-rc.4 → 2.0.0-rc.6

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 (87) hide show
  1. package/dist/headless.cjs +1 -1
  2. package/dist/iab/styles.css +1 -0
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/libs/browser-initial-data.cjs +1 -0
  6. package/dist/libs/browser-initial-data.js +1 -0
  7. package/dist/libs/initial-data.cjs +1 -1
  8. package/dist/libs/initial-data.js +1 -1
  9. package/dist/styles.css +1 -0
  10. package/dist/types.cjs +1 -1
  11. package/dist/version.cjs +1 -1
  12. package/dist/version.js +1 -1
  13. package/dist-types/headless.d.ts +1 -0
  14. package/{dist → dist-types}/index.d.ts +4 -3
  15. package/dist-types/libs/browser-initial-data.d.ts +9 -0
  16. package/{dist → dist-types}/libs/initial-data.d.ts +1 -2
  17. package/dist-types/types.d.ts +38 -0
  18. package/dist-types/version.d.ts +1 -0
  19. package/docs/README.md +73 -0
  20. package/docs/building-headless-components.md +250 -0
  21. package/docs/callbacks.md +117 -0
  22. package/docs/components/consent-banner.md +174 -0
  23. package/docs/components/consent-dialog-link.md +59 -0
  24. package/docs/components/consent-dialog-trigger.md +103 -0
  25. package/docs/components/consent-dialog.md +137 -0
  26. package/docs/components/consent-manager-provider.md +423 -0
  27. package/docs/components/consent-widget.md +78 -0
  28. package/docs/components/dev-tools.md +63 -0
  29. package/docs/components/frame.md +73 -0
  30. package/docs/concepts/client-modes.md +163 -0
  31. package/docs/concepts/consent-categories.md +97 -0
  32. package/docs/concepts/consent-models.md +116 -0
  33. package/docs/concepts/cookie-management.md +122 -0
  34. package/docs/concepts/glossary.md +23 -0
  35. package/docs/concepts/initialization-flow.md +141 -0
  36. package/docs/concepts/policy-packs.md +229 -0
  37. package/docs/headless.md +184 -0
  38. package/docs/hooks/use-color-scheme.md +40 -0
  39. package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
  40. package/docs/hooks/use-consent-manager/location-info.md +95 -0
  41. package/docs/hooks/use-consent-manager/overview.md +390 -0
  42. package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
  43. package/docs/hooks/use-draggable.md +57 -0
  44. package/docs/hooks/use-focus-trap.md +41 -0
  45. package/docs/hooks/use-reduced-motion.md +35 -0
  46. package/docs/hooks/use-ssr-status.md +31 -0
  47. package/docs/hooks/use-text-direction.md +49 -0
  48. package/docs/hooks/use-translations.md +117 -0
  49. package/docs/iab/consent-banner.md +95 -0
  50. package/docs/iab/consent-dialog.md +135 -0
  51. package/docs/iab/overview.md +119 -0
  52. package/docs/iab/use-gvl-data.md +208 -0
  53. package/docs/iframe-blocking.md +107 -0
  54. package/docs/integrations/databuddy.md +186 -0
  55. package/docs/integrations/google-tag-manager.md +153 -0
  56. package/docs/integrations/google-tag.md +149 -0
  57. package/docs/integrations/linkedin-insights.md +109 -0
  58. package/docs/integrations/meta-pixel.md +342 -0
  59. package/docs/integrations/microsoft-uet.md +112 -0
  60. package/docs/integrations/overview.md +89 -0
  61. package/docs/integrations/posthog.md +177 -0
  62. package/docs/integrations/tiktok-pixel.md +113 -0
  63. package/docs/integrations/x-pixel.md +143 -0
  64. package/docs/internationalization.md +197 -0
  65. package/docs/network-blocker.md +178 -0
  66. package/docs/optimization.md +156 -0
  67. package/docs/policy-packs.md +246 -0
  68. package/docs/quickstart.md +155 -0
  69. package/docs/script-loader.md +300 -0
  70. package/docs/server-side.md +171 -0
  71. package/docs/styling/classnames.md +84 -0
  72. package/docs/styling/color-scheme.md +82 -0
  73. package/docs/styling/css-variables.md +92 -0
  74. package/docs/styling/overview.md +312 -0
  75. package/docs/styling/slots.md +93 -0
  76. package/docs/styling/tailwind.md +115 -0
  77. package/docs/styling/tokens.md +214 -0
  78. package/docs/troubleshooting.md +146 -0
  79. package/package.json +20 -13
  80. package/dist/headless.d.ts +0 -2
  81. package/dist/headless.d.ts.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/libs/initial-data.d.ts.map +0 -1
  84. package/dist/types.d.ts +0 -16
  85. package/dist/types.d.ts.map +0 -1
  86. package/dist/version.d.ts +0 -2
  87. package/dist/version.d.ts.map +0 -1
@@ -0,0 +1,246 @@
1
+ ---
2
+ title: Policy Packs
3
+ description: Configure regional consent policies in Next.js — hosted mode, presets, and offline fallback.
4
+ ---
5
+ Policy packs configure how c15t handles regional consent — which model (opt-in, opt-out, none), which categories, and what UI to show. The backend resolves the right policy automatically based on the visitor's location.
6
+
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
+
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.
10
+
11
+ > ℹ️ **Info:**
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.
13
+
14
+ ## Hosted Mode (Recommended)
15
+
16
+ When using consent.io or a self-hosted backend, the provider connects automatically. No policy configuration is needed on the frontend:
17
+
18
+ ```tsx
19
+ <ConsentManagerProvider
20
+ options={{
21
+ backendURL: 'https://your-instance.c15t.dev',
22
+ }}
23
+ >
24
+ <ConsentBanner />
25
+ <ConsentDialog />
26
+ {children}
27
+ </ConsentManagerProvider>
28
+ ```
29
+
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
+
32
+ ## Offline Presets (Fallback)
33
+
34
+ When no backend is available, pass presets directly to the provider:
35
+
36
+ ```tsx
37
+ import { policyPackPresets } from '@c15t/react';
38
+
39
+ offlinePolicy: {
40
+ i18n: {
41
+ defaultProfile: 'default',
42
+ messages: {
43
+ default: {
44
+ translations: {
45
+ en: { cookieBanner: { title: 'Privacy choices' } },
46
+ },
47
+ },
48
+ eu: {
49
+ fallbackLanguage: 'en',
50
+ translations: {
51
+ en: { cookieBanner: { title: 'EU GDPR Consent' } },
52
+ fr: { cookieBanner: { title: 'Consentement RGPD' } },
53
+ de: { cookieBanner: { title: 'GDPR-Einwilligung' } },
54
+ },
55
+ },
56
+ },
57
+ },
58
+ policyPacks: [
59
+ {
60
+ ...policyPackPresets.europeOptIn(),
61
+ i18n: { messageProfile: 'eu' },
62
+ },
63
+ policyPackPresets.californiaOptOut(),
64
+ policyPackPresets.worldNoBanner(),
65
+ ],
66
+ }
67
+ ```
68
+
69
+ Available presets:
70
+
71
+ |Preset|Model|Matches|
72
+ |--|--|--|
73
+ |`europeOptIn()`|`opt-in`|EEA + UK countries + geo fallback|
74
+ |`europeIab()`|`iab`|EEA + UK countries + geo fallback (TCF 2.3)|
75
+ |`californiaOptOut()`|`opt-out`|US-CA region|
76
+ |`quebecOptIn()`|`opt-in`|CA-QC region|
77
+ |`worldNoBanner()`|`none`|default fallback|
78
+
79
+ ## Next.js Example (Hosted)
80
+
81
+ Point the provider at your backend — policy resolution happens server-side:
82
+
83
+ ```tsx title="components/consent-manager/provider.tsx"
84
+ 'use client';
85
+
86
+ import type { ReactNode } from 'react';
87
+ import {
88
+ ConsentBanner,
89
+ ConsentDialog,
90
+ ConsentManagerProvider,
91
+ } from '@c15t/nextjs';
92
+
93
+ export function ConsentManager({ children }: { children: ReactNode }) {
94
+ return (
95
+ <ConsentManagerProvider
96
+ options={{
97
+ backendURL: 'https://your-instance.c15t.dev',
98
+ }}
99
+ >
100
+ <ConsentBanner />
101
+ <ConsentDialog />
102
+ {children}
103
+ </ConsentManagerProvider>
104
+ );
105
+ }
106
+ ```
107
+
108
+ For production Next.js apps, you can optionally hydrate the first `/init` response with the [server-side utilities](/docs/frameworks/next/server-side).
109
+
110
+ ## Offline / Fallback
111
+
112
+ For local development, static sites, or when the backend is unreachable, pass policies directly:
113
+
114
+ ```tsx title="components/consent-manager/provider.tsx"
115
+ 'use client';
116
+
117
+ import type { ReactNode } from 'react';
118
+ import {
119
+ ConsentBanner,
120
+ ConsentDialog,
121
+ ConsentManagerProvider,
122
+ policyPackPresets,
123
+ } from '@c15t/nextjs';
124
+
125
+ export function ConsentManager({ children }: { children: ReactNode }) {
126
+ return (
127
+ <ConsentManagerProvider
128
+ options={{
129
+ mode: 'offline',
130
+ offlinePolicy: {
131
+ policyPacks: [
132
+ policyPackPresets.europeOptIn(),
133
+ policyPackPresets.californiaOptOut(),
134
+ policyPackPresets.worldNoBanner(),
135
+ ],
136
+ },
137
+ overrides: {
138
+ country: 'DE', // Preview as a German visitor
139
+ },
140
+ }}
141
+ >
142
+ <ConsentBanner />
143
+ <ConsentDialog />
144
+ {children}
145
+ </ConsentManagerProvider>
146
+ );
147
+ }
148
+ ```
149
+
150
+ ## Provider Shape
151
+
152
+ Configure packs through `offlinePolicy.policyPacks`. Add `offlinePolicy.i18n`
153
+ when you want offline mode to mirror hosted policy-profile language behavior:
154
+
155
+ ```tsx
156
+ <ConsentManagerProvider
157
+ options={{
158
+ mode: 'offline',
159
+ offlinePolicy: {
160
+ i18n: {
161
+ defaultProfile: 'default',
162
+ messages: {
163
+ default: {
164
+ translations: {
165
+ en: { cookieBanner: { title: 'Privacy choices' } },
166
+ },
167
+ },
168
+ qc: {
169
+ fallbackLanguage: 'fr',
170
+ translations: {
171
+ en: { cookieBanner: { title: 'Quebec Privacy Settings' } },
172
+ fr: { cookieBanner: { title: 'Paramètres de confidentialité du Québec' } },
173
+ },
174
+ },
175
+ },
176
+ },
177
+ policyPacks: [
178
+ {
179
+ id: 'qc_opt_in',
180
+ match: { regions: [{ country: 'CA', region: 'QC' }] },
181
+ i18n: { messageProfile: 'qc' },
182
+ consent: { model: 'opt-in', expiryDays: 365 },
183
+ ui: { mode: 'banner' },
184
+ },
185
+ {
186
+ id: 'default',
187
+ match: { isDefault: true },
188
+ consent: { model: 'none' },
189
+ ui: { mode: 'none' },
190
+ },
191
+ ],
192
+ },
193
+ overrides: { country: 'CA', region: 'QC' },
194
+ }}
195
+ >
196
+ ```
197
+
198
+ With that setup, offline mode resolves language the same way as hosted mode:
199
+
200
+ * the active policy profile defines the allowed language set
201
+ * each profile can define its own `fallbackLanguage`
202
+ * built-in translations only fill missing keys for the selected language
203
+
204
+ ## Fallback Behavior
205
+
206
+ |Configuration|Result|
207
+ |--|--|
208
+ |`offlinePolicy.policyPacks` omitted|Synthetic opt-in fallback banner (also used for hosted network fallback)|
209
+ |`offlinePolicy: { policyPacks: [] }`|Explicit no-banner mode|
210
+ |Non-empty pack, no match, no default|Explicit no-banner mode|
211
+
212
+ 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.
213
+
214
+ ## QA and Debugging
215
+
216
+ The best way to test regional consent behavior is with the [c15t DevTools](/docs/frameworks/react/dev-tools). The DevTools Policy panel lets you simulate different countries, regions, and GPC signals against your real backend — no code changes needed.
217
+
218
+ For deeper inspection:
219
+
220
+ * Read `policy` and `policyDecision` from `useConsentManager()` to see the resolved config
221
+ * Open the DevTools Policy panel to inspect matcher resolution and fingerprints
222
+ * Compare your frontend preview with the backend `/init` response before shipping
223
+
224
+ If you need fully deterministic resolution without a backend (e.g., in automated tests or Storybook), pair `offlinePolicy.policyPacks` with `overrides`:
225
+
226
+ ```tsx
227
+ options={{
228
+ mode: 'offline',
229
+ offlinePolicy: {
230
+ policyPacks: [
231
+ policyPackPresets.europeOptIn(),
232
+ policyPackPresets.californiaOptOut(),
233
+ policyPackPresets.worldNoBanner(),
234
+ ],
235
+ },
236
+ overrides: {
237
+ country: 'US',
238
+ region: 'CA',
239
+ language: 'en-US',
240
+ gpc: true, // Simulate Global Privacy Control
241
+ },
242
+ }}
243
+ ```
244
+
245
+ > ℹ️ **Info:**
246
+ > For the full concept model (matchers, scope modes, re-prompting), see Policy Packs concepts. For backend setup, see the self-host guide.
@@ -0,0 +1,155 @@
1
+ ---
2
+ title: Quickstart
3
+ description: Add consent management to your Next.js app in under 5 minutes.
4
+ lastModified: 2026-03-11
5
+ availableIn:
6
+ - framework: 'javascript'
7
+ url: '/docs/frameworks/javascript/quickstart'
8
+ title: 'JavaScript'
9
+ - framework: 'react'
10
+ url: '/docs/frameworks/react/quickstart'
11
+ title: 'React'
12
+ - framework: 'next'
13
+ url: '/docs/frameworks/next/quickstart'
14
+ title: 'Next.js'
15
+ ---
16
+ ## Via CLI
17
+
18
+ |Package manager|Command|
19
+ |:--|:--|
20
+ |npm|`npx @c15t/cli@rc`|
21
+ |pnpm|`pnpm dlx @c15t/cli@rc`|
22
+ |yarn|`yarn dlx @c15t/cli@rc`|
23
+ |bun|`bunx @c15t/cli@rc`|
24
+
25
+ ## Manual Installation
26
+
27
+ 1. **Install package**
28
+
29
+ |Package manager|Command|
30
+ |:--|:--|
31
+ |npm|`npm install @c15t/nextjs`|
32
+ |pnpm|`pnpm add @c15t/nextjs`|
33
+ |yarn|`yarn add @c15t/nextjs`|
34
+ |bun|`bun add @c15t/nextjs`|
35
+
36
+ 2. **Import styles** Import the prebuilt component stylesheet in your root layout. This is required for styled components to render correctly.
37
+
38
+ ```tsx
39
+ import '@c15t/nextjs/styles.css';
40
+ ```
41
+
42
+ > ℹ️ Info:
43
+ >
44
+ > If you are using the headless API or fully custom styling, you can skip this import.
45
+
46
+ 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
+
48
+ ```tsx
49
+ 'use client';
50
+
51
+ import { type ReactNode } from 'react';
52
+ import {
53
+ ConsentManagerProvider,
54
+ ConsentBanner,
55
+ ConsentDialog,
56
+ } from '@c15t/nextjs';
57
+
58
+ export default function ConsentManagerClient({ children }: { children: ReactNode }) {
59
+ return (
60
+ <ConsentManagerProvider
61
+ options={{
62
+ mode: 'hosted',
63
+ backendURL: 'https://your-instance.c15t.dev',
64
+ consentCategories: ['necessary', 'measurement', 'marketing'],
65
+ // Shows banner during development. Remove for production.
66
+ overrides: { country: 'DE' },
67
+ }}
68
+ >
69
+ <ConsentBanner />
70
+ <ConsentDialog />
71
+ {children}
72
+ </ConsentManagerProvider>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ```tsx
78
+ import type { ReactNode } from 'react';
79
+ import ConsentManagerProvider from './provider';
80
+
81
+ export function ConsentManager({ children }: { children: ReactNode }) {
82
+ return <ConsentManagerProvider>{children}</ConsentManagerProvider>;
83
+ }
84
+ ```
85
+
86
+ > ℹ️ Info:
87
+ >
88
+ > 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.
89
+
90
+ 4. **Mount ConsentManager at the app root** Wrap your app tree with ConsentManager so all routes/components can access consent state.
91
+
92
+ ```tsx
93
+ import { ConsentManager } from '@/components/consent-manager';
94
+
95
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
96
+ return (
97
+ <html lang="en">
98
+ <body>
99
+ <ConsentManager>{children}</ConsentManager>
100
+ </body>
101
+ </html>
102
+ );
103
+ }
104
+ ```
105
+
106
+ 5. **Verify it works** Start your development server and confirm:
107
+
108
+ A consent banner appears at the bottom of the pageClicking "Customize" opens a dialog with toggles for each consent categoryAfter accepting or rejecting, the banner dismisses and your choice persists across page reloads
109
+
110
+ > ℹ️ **Info:**
111
+ > Want better performance and static-route support? See Optimization for rewrites, prefetching, and network tuning. If you want server-side data prefetching, see Server-Side Data Fetching.
112
+
113
+ ## Optional: Add DevTools
114
+
115
+ Install DevTools only if you want a runtime inspector while building and debugging:
116
+
117
+ |Package manager|Command|
118
+ |:--|:--|
119
+ |npm|`npm install @c15t/dev-tools`|
120
+ |pnpm|`pnpm add @c15t/dev-tools`|
121
+ |yarn|`yarn add @c15t/dev-tools`|
122
+ |bun|`bun add @c15t/dev-tools`|
123
+
124
+ Then add it inside your existing provider:
125
+
126
+ ```tsx title="components/consent-manager/provider.tsx"
127
+ import { DevTools } from '@c15t/dev-tools/react';
128
+
129
+ // ...
130
+
131
+ <ConsentManagerProvider options={...}>
132
+ <ConsentBanner />
133
+ <ConsentDialog />
134
+ {process.env.NODE_ENV !== 'production' && <DevTools />}
135
+ {children}
136
+ </ConsentManagerProvider>
137
+ ```
138
+
139
+ > ℹ️ **Info:**
140
+ > Want to understand what's happening under the hood? See Initialization Flow for the lifecycle and Cookie Management for script/cookie behavior and revocation handling.
141
+
142
+ ## Optional: AI Agents
143
+
144
+ Install c15t agent skills to let AI agents help with styling, i18n, scripts & other configuration.
145
+
146
+ |Package manager|Command|
147
+ |:--|:--|
148
+ |npm|`npx @c15t/cli@rc skills`|
149
+ |pnpm|`pnpm dlx @c15t/cli@rc skills`|
150
+ |yarn|`yarn dlx @c15t/cli@rc skills`|
151
+ |bun|`bunx @c15t/cli@rc skills`|
152
+
153
+ See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
154
+
155
+ ## Next steps
@@ -0,0 +1,300 @@
1
+ ---
2
+ title: Script Loader
3
+ description: Gate third-party scripts behind consent - load Google Analytics, Meta Pixel, and other tracking scripts only when users grant permission.
4
+ ---
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
+
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).
8
+
9
+ |Package manager|Command|
10
+ |:--|:--|
11
+ |npm|`npm install @c15t/scripts`|
12
+ |pnpm|`pnpm add @c15t/scripts`|
13
+ |yarn|`yarn add @c15t/scripts`|
14
+ |bun|`bun add @c15t/scripts`|
15
+
16
+ > ℹ️ **Info:**
17
+ > We recommend using the pre-built integrations when possible.
18
+
19
+ ## Basic Usage
20
+
21
+ Pass an array of `Script` objects to the provider:
22
+
23
+ ```tsx
24
+ import { type ReactNode } from 'react';
25
+ import { ConsentManagerProvider } from '@c15t/nextjs';
26
+ import { metaPixel } from '@c15t/scripts/meta-pixel';
27
+
28
+ export function ConsentManager({ children }: { children: ReactNode }) {
29
+ return (
30
+ <ConsentManagerProvider
31
+ options={{
32
+ mode: 'hosted',
33
+ backendURL: '/api/c15t',
34
+ scripts: [
35
+ metaPixel({ pixelId: '123456' }),
36
+ {
37
+ id: 'custom-analytics',
38
+ src: 'https://cdn.example.com/analytics.js',
39
+ category: 'measurement',
40
+ },
41
+ ],
42
+ }}
43
+ >
44
+ {children}
45
+ </ConsentManagerProvider>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ## Script Types
51
+
52
+ ### Standard Scripts
53
+
54
+ Load an external JavaScript file via a `<script>` tag. Use `src` to specify the URL.
55
+
56
+ ### Inline Scripts
57
+
58
+ Execute inline JavaScript code. Use `textContent` instead of `src`:
59
+
60
+ ```tsx
61
+ {
62
+ id: 'gtag-config',
63
+ textContent: `
64
+ window.dataLayer = window.dataLayer || [];
65
+ function gtag(){dataLayer.push(arguments);}
66
+ gtag('js', new Date());
67
+ gtag('config', 'G-XXXXXX');
68
+ `,
69
+ category: 'measurement',
70
+ }
71
+ ```
72
+
73
+ ### Callback-Only Scripts
74
+
75
+ Don't inject any `<script>` tag - just execute callbacks based on consent changes. Useful for controlling libraries that are already loaded:
76
+
77
+ ```tsx
78
+ {
79
+ id: 'posthog-consent',
80
+ callbackOnly: true,
81
+ category: 'measurement',
82
+ onLoad: ({ hasConsent }) => {
83
+ if (hasConsent) {
84
+ posthog.opt_in_capturing();
85
+ }
86
+ },
87
+ onConsentChange: ({ hasConsent }) => {
88
+ if (hasConsent) {
89
+ posthog.opt_in_capturing();
90
+ } else {
91
+ posthog.opt_out_capturing();
92
+ }
93
+ },
94
+ }
95
+ ```
96
+
97
+ ## Consent Conditions
98
+
99
+ The `category` field accepts a `HasCondition` - either a simple string or a logical expression:
100
+
101
+ ```tsx
102
+ // Simple: requires measurement consent
103
+ { category: 'measurement' }
104
+
105
+ // AND: requires both measurement and marketing
106
+ { category: { and: ['measurement', 'marketing'] } }
107
+
108
+ // OR: requires either measurement or marketing
109
+ { category: { or: ['measurement', 'marketing'] } }
110
+ ```
111
+
112
+ ## Script Callbacks
113
+
114
+ Every script supports four lifecycle callbacks:
115
+
116
+ |Callback|When|Use Case|
117
+ |--|--|--|
118
+ |`onBeforeLoad`|Before the script tag is injected|Set up global variables|
119
+ |`onLoad`|Script loaded successfully|Initialize the library|
120
+ |`onError`|Script failed to load|Log error, load fallback|
121
+ |`onConsentChange`|Consent state changed (script already loaded)|Toggle tracking on/off|
122
+
123
+ ```tsx
124
+ {
125
+ id: 'analytics',
126
+ src: 'https://analytics.example.com/v2.js',
127
+ category: 'measurement',
128
+ onBeforeLoad: ({ id }) => {
129
+ console.log(`Loading script: ${id}`);
130
+ },
131
+ onLoad: ({ element }) => {
132
+ window.analytics.init('my-key');
133
+ },
134
+ onError: ({ error }) => {
135
+ console.error('Failed to load analytics:', error);
136
+ },
137
+ onConsentChange: ({ hasConsent, consents }) => {
138
+ window.analytics.setConsent(hasConsent);
139
+ },
140
+ }
141
+ ```
142
+
143
+ ## Advanced Options
144
+
145
+ ### Always Load
146
+
147
+ Scripts that manage their own consent internally (like GTM in consent mode):
148
+
149
+ ```tsx
150
+ {
151
+ id: 'google-tag-manager',
152
+ src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXX',
153
+ category: 'measurement',
154
+ alwaysLoad: true, // Loads regardless of consent state
155
+ }
156
+ ```
157
+
158
+ ### Persist After Revocation
159
+
160
+ Keep the script loaded even after consent is revoked (the page won't reload for this script):
161
+
162
+ ```tsx
163
+ {
164
+ id: 'error-tracking',
165
+ src: 'https://errors.example.com/track.js',
166
+ category: 'measurement',
167
+ persistAfterConsentRevoked: true,
168
+ }
169
+ ```
170
+
171
+ ### Script Placement
172
+
173
+ Control where in the DOM the script is injected:
174
+
175
+ ```tsx
176
+ {
177
+ id: 'widget',
178
+ src: 'https://widget.example.com/embed.js',
179
+ category: 'experience',
180
+ target: 'body', // 'head' (default) or 'body'
181
+ }
182
+ ```
183
+
184
+ ### Ad Blocker Evasion
185
+
186
+ Script element IDs are anonymized by default to avoid ad blocker pattern matching:
187
+
188
+ ```tsx
189
+ {
190
+ id: 'analytics',
191
+ src: '...',
192
+ category: 'measurement',
193
+ anonymizeId: true, // default: true
194
+ }
195
+ ```
196
+
197
+ ## Dynamic Script Management
198
+
199
+ Add, remove, or check scripts at runtime via `useConsentManager()`:
200
+
201
+ ```tsx
202
+ import { useConsentManager } from '@c15t/nextjs';
203
+
204
+ function ScriptManager() {
205
+ const { setScripts, removeScript, isScriptLoaded, getLoadedScriptIds } = useConsentManager();
206
+
207
+ // Add scripts dynamically
208
+ setScripts([{ id: 'dynamic', src: '...', category: 'measurement' }]);
209
+
210
+ // Remove a script
211
+ removeScript('dynamic');
212
+
213
+ // Check if a script is loaded
214
+ const loaded = isScriptLoaded('google-analytics');
215
+
216
+ // Get all loaded script IDs
217
+ const allLoaded = getLoadedScriptIds();
218
+ }
219
+ ```
220
+
221
+ ## API Reference
222
+
223
+ ### Script
224
+
225
+ |Property|Type|Description|Default|Required|
226
+ |:--|:--|:--|:--|:--:|
227
+ |id|string|Unique identifier for the script|-|✅ Required|
228
+ |src|string \|undefined|URL of the script to load|-|Optional|
229
+ |textContent|string \|undefined|Inline JavaScript code to execute|-|Optional|
230
+ |category|HasCondition\<AllConsentNames>|Consent category or condition required to load this script|-|✅ Required|
231
+ |callbackOnly|boolean \|undefined|Whether this is a callback-only script that doesn't need to load an external resource. When true, no script tag will be added to the DOM, only callbacks will be executed.|false|Optional|
232
+ |persistAfterConsentRevoked|boolean \|undefined|Whether the script should persist after consent is revoked.|false|Optional|
233
+ |alwaysLoad|boolean \|undefined|Whether the script should always load regardless of consent state. This is useful for scripts like Google Tag Manager or PostHog that manage their own consent state internally. The script will load immediately and never be unloaded based on consent changes. Note: When using this option, you are responsible for ensuring the script itself respects user consent preferences through its own consent management.|false|Optional|
234
+ |fetchPriority|"high" \|"low" \|"auto" \|undefined|Priority hint for browser resource loading|-|Optional|
235
+ |attributes|Record\<string, string> \|undefined|Additional attributes to add to the script element|-|Optional|
236
+ |async|boolean \|undefined|Whether to use async loading|-|Optional|
237
+ |defer|boolean \|undefined|Whether to defer script loading|-|Optional|
238
+ |nonce|string \|undefined|Content Security Policy nonce|-|Optional|
239
+ |anonymizeId|boolean \|undefined|Whether to use an anonymized ID for the script element, this helps ensure the script is not blocked by ad blockers|true|Optional|
240
+ |target|"head" \|"body" \|undefined|Where to inject the script element in the DOM. Options: \`'head'\`: Scripts are appended to \`\<head>\` (default); \`'body'\`: Scripts are appended to \`\<body>\`|'head'|Optional|
241
+ |onBeforeLoad|Object \|undefined|Callback executed before the script is loaded|-|Optional|
242
+ |onLoad|Object \|undefined|Callback executed when the script loads successfully|-|Optional|
243
+ |onError|Object \|undefined|Callback executed if the script fails to load|-|Optional|
244
+ |onConsentChange|Object \|undefined|Callback executed whenever the consent store is changed. This callback only applies to scripts already loaded.|-|Optional|
245
+ |vendorId|string \|number \|undefined|IAB TCF vendor ID - links script to a registered vendor. When in IAB mode, the script will only load if this vendor has consent. Takes precedence over \`category\` when in IAB mode. Use custom vendor IDs (string or number) to gate non-IAB vendors too.|-|Optional|
246
+ |iabPurposes|number\[] \|undefined|IAB TCF purpose IDs this script requires consent for. When in IAB mode and no vendorId is set, the script will only load if ALL specified purposes have consent.|-|Optional|
247
+ |iabLegIntPurposes|number\[] \|undefined|IAB TCF legitimate interest purpose IDs. These purposes can operate under legitimate interest instead of consent. The script loads if all iabPurposes have consent OR all iabLegIntPurposes have legitimate interest established.|-|Optional|
248
+ |iabSpecialFeatures|number\[] \|undefined|IAB TCF special feature IDs this script requires. Options: 1: Use precise geolocation data; 2: Actively scan device characteristics for identification|-|Optional|
249
+
250
+ #### `onBeforeLoad`
251
+
252
+ Callback executed before the script is loaded
253
+
254
+ |Property|Type|Description|Default|Required|
255
+ |:--|:--|:--|:--|:--:|
256
+ |id|string|The original script ID|-|✅ Required|
257
+ |elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
258
+ |hasConsent|boolean|Has consent|-|✅ Required|
259
+ |consents|ConsentState|The current consent state|-|✅ Required|
260
+ |element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
261
+ |error|Error \|undefined|Error information (for error callbacks)|-|Optional|
262
+
263
+ #### `onLoad`
264
+
265
+ Callback executed when the script loads successfully
266
+
267
+ |Property|Type|Description|Default|Required|
268
+ |:--|:--|:--|:--|:--:|
269
+ |id|string|The original script ID|-|✅ Required|
270
+ |elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
271
+ |hasConsent|boolean|Has consent|-|✅ Required|
272
+ |consents|ConsentState|The current consent state|-|✅ Required|
273
+ |element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
274
+ |error|Error \|undefined|Error information (for error callbacks)|-|Optional|
275
+
276
+ #### `onError`
277
+
278
+ Callback executed if the script fails to load
279
+
280
+ |Property|Type|Description|Default|Required|
281
+ |:--|:--|:--|:--|:--:|
282
+ |id|string|The original script ID|-|✅ Required|
283
+ |elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
284
+ |hasConsent|boolean|Has consent|-|✅ Required|
285
+ |consents|ConsentState|The current consent state|-|✅ Required|
286
+ |element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
287
+ |error|Error \|undefined|Error information (for error callbacks)|-|Optional|
288
+
289
+ #### `onConsentChange`
290
+
291
+ Callback executed whenever the consent store is changed. This callback only applies to scripts already loaded.
292
+
293
+ |Property|Type|Description|Default|Required|
294
+ |:--|:--|:--|:--|:--:|
295
+ |id|string|The original script ID|-|✅ Required|
296
+ |elementId|string|The actual DOM element ID used (anonymized if enabled)|-|✅ Required|
297
+ |hasConsent|boolean|Has consent|-|✅ Required|
298
+ |consents|ConsentState|The current consent state|-|✅ Required|
299
+ |element|HTMLScriptElement \|undefined|The script element (for load/error callbacks) Will be undefined for callback-only scripts|-|Optional|
300
+ |error|Error \|undefined|Error information (for error callbacks)|-|Optional|