@c15t/scripts 3.0.0-alpha.0 → 3.0.0-alpha.2

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 (88) hide show
  1. package/AGENTS.md +7 -0
  2. package/README.md +4 -3
  3. package/dist/e2e-test-utils.js +5 -3
  4. package/dist/engine/runtime.js +14 -3
  5. package/dist/events.js +218 -0
  6. package/dist/registry.js +40 -0
  7. package/dist/vendors/ads-and-pixels/pinterest-tag.js +123 -0
  8. package/dist/vendors/analytics/google-tag.js +14 -2
  9. package/dist/vendors/analytics/microsoft-clarity.js +4 -1
  10. package/dist/vendors/analytics/one-dollar-stats.js +30 -0
  11. package/dist/vendors/analytics/segment.js +10 -1
  12. package/dist/vendors/functional/front-chat.js +64 -0
  13. package/dist/vendors/tag-managers/cloudflare-zaraz.js +98 -0
  14. package/dist/vendors/tag-managers/google-tag-manager.js +17 -3
  15. package/dist-types/__tests__/helpers.d.ts +2 -2
  16. package/dist-types/engine/compile.d.ts +1 -1
  17. package/dist-types/engine/runtime.d.ts +1 -1
  18. package/dist-types/events.d.ts +46 -0
  19. package/dist-types/registry.d.ts +36 -0
  20. package/dist-types/resolve.d.ts +1 -1
  21. package/dist-types/vendors/_shared/install-builders.d.ts +1 -1
  22. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +295 -0
  23. package/dist-types/vendors/analytics/adobe-analytics.d.ts +1 -1
  24. package/dist-types/vendors/analytics/google-tag.d.ts +3 -1
  25. package/dist-types/vendors/analytics/matomo-analytics.d.ts +1 -1
  26. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +39 -0
  27. package/dist-types/vendors/analytics/segment.d.ts +7 -1
  28. package/dist-types/vendors/functional/front-chat.d.ts +62 -0
  29. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +39 -0
  30. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +3 -1
  31. package/docs/README.md +7 -0
  32. package/docs/customization/overview.md +4 -3
  33. package/docs/customization/recipes.md +4 -2
  34. package/docs/customization/tokens.md +66 -3
  35. package/docs/frameworks/javascript/script-loader.md +53 -0
  36. package/docs/frameworks/next/script-loader.md +60 -12
  37. package/docs/frameworks/react/script-loader.md +14 -0
  38. package/docs/guides/consent-state.md +327 -0
  39. package/docs/guides/deployment-modes.md +12 -0
  40. package/docs/guides/shared-consent-controls.md +158 -0
  41. package/docs/integrations/adobe-analytics.md +1 -1
  42. package/docs/integrations/ahrefs-analytics.md +1 -1
  43. package/docs/integrations/amplitude.md +1 -1
  44. package/docs/integrations/building-integrations.md +5 -0
  45. package/docs/integrations/clear-on-revocation.md +167 -0
  46. package/docs/integrations/clearbit.md +1 -1
  47. package/docs/integrations/cloudflare-web-analytics.md +1 -1
  48. package/docs/integrations/cloudflare-zaraz.md +399 -0
  49. package/docs/integrations/crisp.md +1 -1
  50. package/docs/integrations/databuddy.md +1 -1
  51. package/docs/integrations/fathom-analytics.md +1 -1
  52. package/docs/integrations/front-chat.md +322 -0
  53. package/docs/integrations/google-maps.md +21 -21
  54. package/docs/integrations/google-tag-manager.md +1 -1
  55. package/docs/integrations/google-tag.md +1 -1
  56. package/docs/integrations/granular-consent.md +210 -0
  57. package/docs/integrations/heap.md +1 -1
  58. package/docs/integrations/hightouch.md +1 -1
  59. package/docs/integrations/hotjar.md +1 -1
  60. package/docs/integrations/intercom.md +1 -1
  61. package/docs/integrations/linkedin-insights.md +1 -1
  62. package/docs/integrations/logrocket.md +1 -1
  63. package/docs/integrations/matomo-analytics.md +1 -1
  64. package/docs/integrations/meta-pixel.md +1 -1
  65. package/docs/integrations/microsoft-clarity.md +1 -1
  66. package/docs/integrations/microsoft-uet.md +1 -1
  67. package/docs/integrations/mixpanel-analytics.md +1 -1
  68. package/docs/integrations/one-dollar-stats.md +305 -0
  69. package/docs/integrations/openai-pixel.md +1 -1
  70. package/docs/integrations/overview.md +22 -18
  71. package/docs/integrations/pinterest-tag.md +321 -0
  72. package/docs/integrations/pirsch.md +1 -1
  73. package/docs/integrations/plausible-analytics.md +1 -1
  74. package/docs/integrations/posthog.md +1 -1
  75. package/docs/integrations/promptwatch.md +1 -1
  76. package/docs/integrations/reddit-pixel.md +1 -1
  77. package/docs/integrations/rudderstack.md +1 -1
  78. package/docs/integrations/rybbit-analytics.md +1 -1
  79. package/docs/integrations/segment.md +1 -1
  80. package/docs/integrations/snapchat-pixel.md +1 -1
  81. package/docs/integrations/tiktok-pixel.md +1 -1
  82. package/docs/integrations/umami-analytics.md +1 -1
  83. package/docs/integrations/vercel-analytics.md +1 -1
  84. package/docs/integrations/x-pixel.md +1 -1
  85. package/docs/integrations/youtube.md +27 -22
  86. package/docs/upgrade-v3.md +176 -1
  87. package/package.json +30 -2
  88. package/readme.json +0 -19
@@ -0,0 +1,39 @@
1
+ import type { Script } from '@c15t/core';
2
+ /** The classic script from OneDollarStats' installation instructions. */
3
+ export declare const ONE_DOLLAR_STATS_SCRIPT_SRC = "https://assets.onedollarstats.com/stonks.js";
4
+ /** OneDollarStats is loaded only after measurement consent is granted. */
5
+ export declare const oneDollarStatsManifest: {
6
+ readonly kind: "c15t.vendor-manifest";
7
+ readonly schemaVersion: 1;
8
+ readonly category: 'measurement';
9
+ readonly install: [{
10
+ readonly defer: true;
11
+ readonly src: "https://assets.onedollarstats.com/stonks.js";
12
+ readonly type: 'loadScript';
13
+ }];
14
+ readonly vendor: 'one-dollar-stats';
15
+ };
16
+ export interface OneDollarStatsOptions {
17
+ /**
18
+ * Bare hostname, such as `docs.example.com`. Overrides the event hostname
19
+ * on every host, including production. Required for local dev mode.
20
+ */
21
+ hostname?: string;
22
+ /**
23
+ * Tracker settings forwarded as `data-*` attributes. Use string values,
24
+ * e.g. `devmode: 'true'`, `autocollect: 'false'`, or a custom `url`.
25
+ * Setting `'hash-routing': 'false'` omits the presence-based attribute.
26
+ */
27
+ [setting: string]: string | undefined;
28
+ }
29
+ /**
30
+ * Creates a consent-gated OneDollarStats script. No API key is required.
31
+ * The tracker reads data attributes from `document.currentScript` and
32
+ * handles client-side navigation itself.
33
+ *
34
+ * @param options - Tracker settings without the `data-` prefix.
35
+ * @returns The OneDollarStats script configuration.
36
+ * @throws {Error} When hostname is not a bare host, or a setting name is not
37
+ * a valid attribute name or its value is not a string.
38
+ */
39
+ export declare const oneDollarStats: (options?: OneDollarStatsOptions) => Script;
@@ -131,6 +131,10 @@ export declare const segmentManifest: {
131
131
  }];
132
132
  readonly category: 'measurement';
133
133
  readonly install: [{
134
+ readonly path: ["analytics", "_loadOptions"];
135
+ readonly type: 'setGlobalPath';
136
+ readonly value: '{{loadOptions}}';
137
+ }, {
134
138
  readonly global: 'analytics';
135
139
  readonly method: 'page';
136
140
  readonly type: 'callGlobal';
@@ -142,6 +146,8 @@ export declare const segmentManifest: {
142
146
  readonly vendor: 'segment';
143
147
  };
144
148
  export interface SegmentOptions {
149
+ /** Options consumed by Analytics.js on initialization. */
150
+ loadOptions?: Record<string, unknown>;
145
151
  /** Your Segment write key. */
146
152
  writeKey: string;
147
153
  /** Queue the initial `analytics.page()` call during setup. */
@@ -155,4 +161,4 @@ export interface SegmentOptions {
155
161
  * @param options - The options for the Segment script.
156
162
  * @returns The Segment script configuration.
157
163
  */
158
- export declare const segment: ({ writeKey, trackPageView, scriptUrl, }: SegmentOptions) => Script;
164
+ export declare const segment: ({ writeKey, loadOptions, trackPageView, scriptUrl, }: SegmentOptions) => Script;
@@ -0,0 +1,62 @@
1
+ import type { Script } from '@c15t/core';
2
+ declare global {
3
+ interface Window {
4
+ FrontChat?: (command: string, options?: Record<string, unknown>) => unknown;
5
+ }
6
+ }
7
+ /** Front Chat loader and consent-gated widget initialisation. */
8
+ export declare const frontChatManifest: {
9
+ readonly kind: "c15t.vendor-manifest";
10
+ readonly schemaVersion: 1;
11
+ readonly category: 'functionality';
12
+ readonly install: [{
13
+ readonly async: true;
14
+ readonly src: '{{scriptSrc}}';
15
+ readonly type: 'loadScript';
16
+ }];
17
+ readonly onLoadGranted: [{
18
+ readonly args: ["init", "{{initOptions}}"];
19
+ readonly global: 'FrontChat';
20
+ readonly type: 'callGlobal';
21
+ }];
22
+ readonly vendor: 'front-chat';
23
+ };
24
+ export interface FrontChatOptions {
25
+ /** Public chat ID from the Front channel's installation snippet. */
26
+ chatId: string;
27
+ /** Show Front's default launcher. @default true */
28
+ useDefaultLauncher?: boolean;
29
+ /** Custom or proxied Front Chat bundle URL. */
30
+ scriptSrc?: string;
31
+ /** CSP nonce forwarded to the loader and Front's generated scripts. */
32
+ nonce?: string;
33
+ }
34
+ /**
35
+ * Creates a Front Chat script gated on functionality consent.
36
+ *
37
+ * Keep c15t's default reload-on-revocation behaviour enabled to stop an
38
+ * already-running widget. Removing its loader cannot unload the SDK.
39
+ *
40
+ * @param options - Front channel and launcher configuration.
41
+ * @returns The Front Chat script configuration.
42
+ * @throws {Error} When chatId is missing or empty. Copy the public ID from
43
+ * the Front channel's installation snippet.
44
+ * @example
45
+ * frontChat({ chatId: 'YOUR_FRONT_CHAT_ID' });
46
+ * @see https://help.front.com/en/articles/2049
47
+ */
48
+ export declare const frontChat: (options: FrontChatOptions) => Script;
49
+ /**
50
+ * Requests widget removal and session cleanup through Front's SDK.
51
+ *
52
+ * Optionally call it from `onBeforeConsentRevocationReload`. Front does not
53
+ * provide a completion promise, so this does not guarantee cleanup before
54
+ * navigation.
55
+ * Safe to call during SSR or before the SDK loads.
56
+ *
57
+ * @returns Nothing.
58
+ * @example
59
+ * shutdownFrontChat();
60
+ * @see https://dev.frontapp.com/docs/chat-sdk-reference
61
+ */
62
+ export declare const shutdownFrontChat: () => void;
@@ -0,0 +1,39 @@
1
+ import type { AllConsentNames, Script } from '@c15t/core';
2
+ /** The Zaraz Consent API methods used by the bridge. */
3
+ export interface ZarazConsentApi {
4
+ APIReady: boolean;
5
+ modal?: unknown;
6
+ getAll: () => Record<string, boolean>;
7
+ set: (permissions: Record<string, boolean>) => void;
8
+ sendQueuedEvents: () => void;
9
+ }
10
+ export interface CloudflareZarazOptions {
11
+ /** Map c15t categories to purpose IDs from the Zaraz dashboard. */
12
+ purposes: Partial<Record<AllConsentNames, readonly string[]>>;
13
+ /** Hide the currently visible Zaraz modal. Disable auto-display in Zaraz too. @default true */
14
+ hideBuiltInModal?: boolean;
15
+ /** Replay Zaraz's queued pageview events after a denied purpose becomes allowed. @default true */
16
+ sendQueuedEvents?: boolean;
17
+ /**
18
+ * Called once after the first successful synchronization. With automatic
19
+ * pageviews disabled in Zaraz, send the initial Pageview from this callback.
20
+ */
21
+ onReady?: () => void;
22
+ /** Called when synchronization fails. Retried on the next consent update or readiness event. */
23
+ onError?: (error: unknown) => void;
24
+ }
25
+ /**
26
+ * Synchronize c15t effective permissions with externally loaded Zaraz tools.
27
+ * Does not load Zaraz or configure tools. Unmapped Zaraz purposes are denied.
28
+ * Disable automatic pageviews and send the first Pageview from onReady to avoid
29
+ * running tools with a stale Zaraz consent cookie before synchronization.
30
+ *
31
+ * @param options - Purpose mapping and synchronization callbacks.
32
+ * @returns A callback-only script for the existing c15t script loader.
33
+ * @throws {Error} If the mapping is empty, contains blank IDs, or maps an ID twice.
34
+ * @example
35
+ * ```ts
36
+ * cloudflareZaraz({ purposes: { measurement: ['analytics-purpose'] } });
37
+ * ```
38
+ */
39
+ export declare const cloudflareZaraz: (options: CloudflareZarazOptions) => Script;
@@ -54,6 +54,8 @@ export declare const googleTagManagerManifest: {
54
54
  readonly vendor: 'google-tag-manager';
55
55
  };
56
56
  export interface GoogleTagManagerOptions {
57
+ /** Container queue name. Defaults to dataLayer. */
58
+ dataLayer?: string;
57
59
  /**
58
60
  * Your Google Tag Manager container ID. Begins with 'GTM-'.
59
61
  * @example `GTM-1234XXX`
@@ -91,4 +93,4 @@ export interface GoogleTagManagerOptions {
91
93
  * @param options - The options for the Google Tag Manager script.
92
94
  * @returns The Google Tag Manager script.
93
95
  */
94
- export declare const googleTagManager: ({ id, updateEventName, consentMapping, }: GoogleTagManagerOptions) => Script;
96
+ export declare const googleTagManager: ({ id, dataLayer, updateEventName, consentMapping, }: GoogleTagManagerOptions) => Script;
package/docs/README.md CHANGED
@@ -30,6 +30,7 @@ These docs describe v3. Start with Inth hosted setup, identify the framework, ro
30
30
  - [Understand consent state](./guides/consent-state.md): Distinguish policy resolution, effective permissions, explicit choices, notices and privacy signals.
31
31
  - [Data fetching and transports](./guides/data-fetching.md): Choose cached manifests, backend init or offline policy resolution, and understand where consent records are saved.
32
32
  - [Choose a deployment mode](./guides/deployment-modes.md): Choose who runs your consent backend, then select manifest, init or offline resolution for your deployment.
33
+ - [Share consent controls across frameworks](./guides/shared-consent-controls.md): Use the same c15t script lifecycle, external consent source, and event controls in every framework.
33
34
  - [Troubleshoot consent](./guides/troubleshooting.md): Diagnose missing banners, early vendor requests, lost choices and hydration differences.
34
35
  - [Verify consent before shipping](./guides/verify-consent.md): Test requests, policy resolution, persistence, navigation and preference changes in a production build.
35
36
 
@@ -47,14 +48,18 @@ These docs describe v3. Start with Inth hosted setup, identify the framework, ro
47
48
  - [Ahrefs Analytics](./integrations/ahrefs-analytics.md): Configure Ahrefs Analytics with c15t v3, understand measurement permission and verify loading and revocation.
48
49
  - [Amplitude](./integrations/amplitude.md): Configure Amplitude with c15t v3, understand measurement permission and verify loading and revocation.
49
50
  - [Custom integrations](./integrations/building-integrations.md): Define loading, initialization and consent-change behavior for a vendor without a helper.
51
+ - [Clear on revocation](./integrations/clear-on-revocation.md): Remove configured first-party cookies and Web Storage keys when their consent category is denied.
50
52
  - [Clearbit](./integrations/clearbit.md): Configure Clearbit with c15t v3, understand marketing permission and verify loading and revocation.
51
53
  - [Cloudflare Web Analytics](./integrations/cloudflare-web-analytics.md): Configure Cloudflare Web Analytics with c15t v3, understand measurement permission and verify loading and revocation.
54
+ - [Cloudflare Zaraz](./integrations/cloudflare-zaraz.md): Synchronize c15t permissions with Zaraz purposes while Cloudflare manages your tools.
52
55
  - [Crisp](./integrations/crisp.md): Configure Crisp with c15t v3, understand functionality permission and verify loading and revocation.
53
56
  - [Databuddy](./integrations/databuddy.md): Configure Databuddy's initial and updated consent state with c15t v3.
54
57
  - [Fathom Analytics](./integrations/fathom-analytics.md): Configure Fathom Analytics with c15t v3, understand measurement permission and verify loading and revocation.
58
+ - [Front Chat](./integrations/front-chat.md): Load the Front Chat widget with functionality permission, forward CSP nonces and clear the session on revocation.
55
59
  - [Google Maps](./integrations/google-maps.md): Prevent a map iframe from mounting before the required permission.
56
60
  - [Google Tag](./integrations/google-tag.md): Configure gtag with c15t Consent Mode signals and understand its loading behavior.
57
61
  - [Google Tag Manager](./integrations/google-tag-manager.md): Load GTM with c15t consent signals and verify the tags inside your container.
62
+ - [Granular consent](./integrations/granular-consent.md): Let visitors grant a category and still turn one vendor off, without adopting IAB TCF.
58
63
  - [Heap](./integrations/heap.md): Configure Heap with c15t v3, understand measurement permission and verify loading and revocation.
59
64
  - [Hightouch](./integrations/hightouch.md): Configure Hightouch with c15t v3, understand measurement permission and verify loading and revocation.
60
65
  - [Hotjar](./integrations/hotjar.md): Configure Hotjar with c15t v3, understand measurement permission and verify loading and revocation.
@@ -66,8 +71,10 @@ These docs describe v3. Start with Inth hosted setup, identify the framework, ro
66
71
  - [Microsoft Clarity](./integrations/microsoft-clarity.md): Configure Microsoft Clarity with c15t v3, understand measurement permission and verify loading and revocation.
67
72
  - [Microsoft UET](./integrations/microsoft-uet.md): Configure Microsoft UET with c15t v3, understand marketing permission and verify loading and revocation.
68
73
  - [Mixpanel](./integrations/mixpanel-analytics.md): Configure Mixpanel with c15t v3, understand measurement permission and verify loading and revocation.
74
+ - [OneDollarStats](./integrations/one-dollar-stats.md): Load the OneDollarStats tracker with measurement permission, forward its settings and verify loading and revocation.
69
75
  - [OpenAI Pixel](./integrations/openai-pixel.md): Configure the OpenAI Measurement Pixel for ChatGPT Ads with c15t v3, manage marketing permission and verify conversion delivery.
70
76
  - [Overview](./integrations/overview.md): Find all c15t integrations for analytics, tag managers, advertising, chat and embedded content.
77
+ - [Pinterest Tag](./integrations/pinterest-tag.md): Configure the Pinterest Tag with c15t v3, track typed events and verify marketing permission, revocation and reload.
71
78
  - [Pirsch](./integrations/pirsch.md): Configure Pirsch with c15t v3, understand measurement permission and verify loading and revocation.
72
79
  - [Plausible Analytics](./integrations/plausible-analytics.md): Configure Plausible Analytics with c15t v3, understand measurement permission and verify loading and revocation.
73
80
  - [PostHog](./integrations/posthog.md): Choose PostHog loading and cookieless behavior, configure the region, and synchronize v3 permissions.
@@ -34,10 +34,11 @@ layout can be restored by the policy renderer.
34
34
  ## Use the adapter's configuration shape
35
35
 
36
36
  React and Next.js accept provider `theme`, `presentation` and `components`
37
- options. Vue and Nuxt expose their own shared configuration, including CSS
37
+ options, and render theme tokens with `ConsentTheme` on the server. Vue and Nuxt expose their own shared configuration, including CSS
38
38
  `tokens` and component slots. Svelte accepts its provider options and theme
39
- contract. Astro serializes integration options and uses the selected adapter for
40
- dialogs. Do not move a configuration object between frameworks without checking
39
+ slots; SvelteKit renders the token CSS from a server `load`. Astro serializes
40
+ integration options, renders the theme tokens on the server and uses the
41
+ selected adapter for dialogs. Do not move a configuration object between frameworks without checking
41
42
  the target types.
42
43
 
43
44
  Read [tokens and CSS](./tokens.md),
@@ -26,8 +26,10 @@ export const brandTheme = defineTheme({
26
26
 
27
27
  ```
28
28
 
29
- Install `@c15t/ui` to use `defineTheme` directly. Pass `brandTheme` as `theme` in
30
- your existing React or Next.js provider options. Keep your Inth mode and script
29
+ Install `@c15t/ui` to use `defineTheme` directly. Render
30
+ `<ConsentTheme theme={brandTheme} />` on the server for the tokens, and pass
31
+ `brandTheme` as `theme` in your existing React or Next.js provider options for
32
+ the action styles. Keep your Inth mode and script
31
33
  configuration. The theme source above is also imported by the runnable example,
32
34
  so the rendered result and the documentation use the same values.
33
35
 
@@ -11,10 +11,53 @@ React, Next.js and Svelte provide `styles.css`. Import the adapter's stylesheet
11
11
  at the app's global entry point. Vue includes styles in its components; Astro
12
12
  adds styles through its integration.
13
13
 
14
+ The stylesheet blocks rendering, so it carries only what a first paint can
15
+ show: the default tokens, every c15t CSS variable, and the rules for the
16
+ banner, `ConsentDialogTrigger` and the `ConsentGate` placeholder. The consent
17
+ dialog and preference widget bring their own rules. Each rule reaches the page
18
+ once.
19
+
14
20
  ```tsx
15
21
  import 'c15t/react/styles.css';
16
22
  ```
17
23
 
24
+ | File | Holds | Loaded by |
25
+ | -------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
26
+ | `styles.css` or `styles.tw3.css` | Default tokens, all variables, banner, trigger and `ConsentGate` rules | Your app, once |
27
+ | `@c15t/ui/styles/dialog.css` | Dialog and preference widget rules | The dialog component, with its lazy chunk |
28
+ | `@c15t/ui/styles/primitives.css` | Rules for the `@c15t/ui/styles/primitives` class maps | Svelte's `styles.css`, or your app if it renders those class maps |
29
+ | `iab/styles.css` | IAB TCF banner and dialog rules and variables | Your app, after `styles.css` |
30
+
31
+ In React, Next.js and TanStack Start, the dialog's module imports
32
+ `@c15t/ui/styles/dialog.css`. The bundler emits it with the dialog's chunk and
33
+ loads it before that chunk runs, so the dialog never renders unstyled. The
34
+ chunk loads after first paint, by the time the dialog first opens. Do not
35
+ import `dialog.css` yourself. The `@c15t/react/components/consent-dialog`,
36
+ `/components/consent-widget`, `/primitives`, `/primitives/*` and `/iab` entry
37
+ points import it as soon as you import them, because they render dialog parts
38
+ outside the lazy chunk.
39
+
40
+ These modules import the stylesheet through `@c15t/ui/styles/dialog`. Under
41
+ the `node` export condition that module imports nothing, so server code that
42
+ loads `@c15t/react` with plain Node, such as the Pages Router or an SSR build
43
+ that keeps dependencies external, does not fail on the `.css` file. In your
44
+ own components, import `@c15t/ui/styles/dialog` rather than the `.css` file
45
+ if the module can run on the server.
46
+
47
+ Svelte loads its dialog with the page, so `c15t/svelte/styles.css` also
48
+ imports the dialog and primitive rules. Astro injects the banner rules; the
49
+ React and Svelte dialog islands import the rest, and Astro links those
50
+ stylesheets on every page, so in Astro the dialog rules still block rendering.
51
+ Vue components import their own stylesheets.
52
+
53
+ The dialog rules sit in `@layer components` and declare no variables, so
54
+ tokens from `theme`, variables you override in your own CSS, and Tailwind 4
55
+ utilities still win over them even though they load later. If you import
56
+ `styles.css` into a named layer, such as `@import 'c15t/react/styles.css'
57
+ layer(c15t)`, the dialog rules still join the top-level `components` layer.
58
+ List `components` in your layer order statement, for example
59
+ `@layer c15t, components, app;`, so it does not land after your own layers.
60
+
18
61
  The standard stylesheet places rules in `@layer components`. For Tailwind 3,
19
62
  use the `styles.tw3.css` entry instead of the standard stylesheet, between the
20
63
  components and utilities directives in your Tailwind entry:
@@ -26,6 +69,18 @@ components and utilities directives in your Tailwind entry:
26
69
  @tailwind utilities;
27
70
  ```
28
71
 
72
+ Tailwind 3 also processes the dialog stylesheet the bundler loads, and it
73
+ rejects a stylesheet that uses `@layer components` without its own
74
+ `@tailwind components` directive. Add the c15t plugin before `tailwindcss` in
75
+ your PostCSS config. It removes the layer wrapper from c15t's stylesheets so
76
+ Tailwind 3 accepts them and its preflight does not override them:
77
+
78
+ ```js title="postcss.config.mjs"
79
+ export default {
80
+ plugins: ['@c15t/ui/postcss-tailwind3', 'tailwindcss', 'autoprefixer'],
81
+ };
82
+ ```
83
+
29
84
  Do not load both c15t stylesheet variants. For Tailwind 4 or unlayered CSS,
30
85
  inspect layer order before reaching for `!important`.
31
86
 
@@ -48,9 +103,17 @@ export const theme = defineTheme({
48
103
  });
49
104
  ```
50
105
 
51
- Install `@c15t/ui` if importing its theme helper directly. Pass `theme` in your
52
- provider options. `consentActions` selects styling by action role. A per-action
53
- entry overrides `primary`, which overrides `default`.
106
+ Install `@c15t/ui` if importing its theme helper directly. The browser does
107
+ not turn tokens into CSS. In React and Next.js, render
108
+ `<ConsentTheme theme={theme} />` where the app renders on the server, and pass
109
+ `theme` in your provider options for `consentActions`. Elsewhere, call
110
+ `generateThemeCSS(theme)` from `@c15t/ui/theme` on the server or at build time
111
+ and put the result in a `<style>` element or your stylesheet. See
112
+ [React styling](https://c15t.com/docs/frameworks/react/styling/overview) and
113
+ [Next.js styling](https://c15t.com/docs/frameworks/next/styling/overview).
114
+
115
+ `consentActions` selects styling by action role. A per-action entry overrides
116
+ `primary`, which overrides `default`.
54
117
 
55
118
  ## Target a prompt with CSS
56
119
 
@@ -45,3 +45,56 @@ assuming all helpers have the same network behavior.
45
45
  The loader exposes `updateScripts`, `getLoadedScriptIds` and `dispose`.
46
46
  Unloading an element cannot reverse requests or code that already ran. Test
47
47
  revocation and vendor cleanup with [verification](../../guides/verify-consent.md).
48
+
49
+ ## Dispose integration resources
50
+
51
+ Ordinary scripts keep their mounted resource when `updateScripts` receives a
52
+ fresh object with the same ID and unchanged element configuration. New callback
53
+ functions alone do not reload the vendor or send a temporary denial. Changing
54
+ the source, inline code or element attributes starts a new loading lifecycle.
55
+ Consent conditions are reevaluated on every configuration update. Pending
56
+ load and error events use the latest registered callbacks and consent state.
57
+ Setting `persistAfterConsentRevoked` to `false` removes an owned retained element
58
+ when the script no longer has consent.
59
+
60
+ Custom script configurations can use `onDispose(info)` to release event
61
+ listeners or other resources. Adding this hook opts the configuration into an
62
+ object-owned lifecycle: replacing the object disposes its resources and starts
63
+ again, even with the same ID. Keep these objects stable across framework
64
+ rerenders. The loader also calls the hook when the configuration is removed or
65
+ the loader is disposed, including configurations that never loaded.
66
+ `info.element` contains the last loaded or retained element when available,
67
+ even when configuration removal has already detached it.
68
+
69
+ Duplicate references receive one cleanup per registration. Re-registering a
70
+ removed object starts a new lifecycle. Updates requested from lifecycle
71
+ callbacks run after the current pass; if several are requested, the latest
72
+ configuration wins. Disposal stops further reconciliation. A callback feedback
73
+ loop exceeding 100 consecutive passes disposes the loader and reports an
74
+ `error` debug event. Avoid callbacks that keep changing consent or replacing
75
+ their own configuration.
76
+
77
+ `onBeforeLoad` prepares a loading attempt. If a callback changes consent or
78
+ replaces configurations, the loader cancels that attempt before loading and
79
+ reevaluates the latest state. A still-eligible script can retry preparation
80
+ with a new element and updated consent. Make `onBeforeLoad` safe to repeat;
81
+ use `onLoad` for initialization that requires a completed load. This also
82
+ applies to callback-only scripts, whose `onLoad` is skipped when preparation
83
+ invalidates the current pass.
84
+
85
+ Consent revocation alone does not call `onDispose`. Use `onConsentChange` for
86
+ vendor opt-out commands. Cleanup errors are reported through the loader's debug
87
+ events and do not prevent other configurations from being cleaned up.
88
+
89
+ ## Clear stored tracking data
90
+
91
+ Script gating does not remove cookies or Web Storage entries that a script
92
+ already wrote. Configure [clear on revocation](../../integrations/clear-on-revocation.md)
93
+ on your runtime, or attach its module to your existing kernel, to remove
94
+ declared data when its category is denied.
95
+
96
+ ## Shared lifecycle controls
97
+
98
+ See [shared consent controls](../../guides/shared-consent-controls.md) for external CMPs,
99
+ preference delegation, withdrawal reloads, and application events. These controls
100
+ use the same core runtime across frameworks.
@@ -108,26 +108,26 @@ do not add a second provider. Keep your site's content and footer inside it.
108
108
 
109
109
  ## Pass prepared consent through your router
110
110
 
111
- App Router awaits the prefetch in the `ResolvedConsent` Server Component from
112
- the [App Router guide](https://c15t.com/docs/frameworks/next/app-router) and renders the
113
- wrapper inside it. This partial example is that component; keep the
114
- `Suspense` boundary, `html`, `body` and stylesheet from your existing layout:
111
+ In the App Router, the root layout from the
112
+ [App Router guide](https://c15t.com/docs/frameworks/next/app-router) passes the pending
113
+ `resolveConsent` result to this wrapper. This partial example is that call;
114
+ keep the `html`, `body` and stylesheet from your existing layout:
115
115
 
116
116
  ```tsx
117
- import type { ReactNode } from 'react';
118
117
  import { resolveConsent } from 'c15t/next/server';
119
118
  import { consentConfig } from '../c15t.config';
120
119
  import { Consent } from '../components/consent';
121
120
 
122
- async function ResolvedConsent({ children }: { children: ReactNode }) {
123
- const state = await resolveConsent({ config: consentConfig });
124
- return <Consent state={state}>{children}</Consent>;
125
- }
121
+ // Inside the existing synchronous root layout:
122
+ const state = resolveConsent({ config: consentConfig });
123
+
124
+ <Consent state={state}>{children}</Consent>
126
125
  ```
127
126
 
128
- To stream the page shell before consent resolves instead, pass the unawaited
129
- promise from a synchronous layout as described in
130
- [stream the page while consent resolves](https://c15t.com/docs/frameworks/next/app-router#stream-the-page-while-consent-resolves).
127
+ No script loads until the browser has applied the resolved policy and the
128
+ visitor's choice allows it. With the
129
+ [awaited layout](https://c15t.com/docs/frameworks/next/app-router#render-the-banner-in-the-server-html),
130
+ pass the awaited result from `ResolvedConsent` to the same wrapper instead.
131
131
 
132
132
  Pages Router passes `state={pageProps.consentState ?? {}}` to this wrapper
133
133
  in `_app.tsx`. Keep `getServerSideProps` and its `c15t/next/pages` helper.
@@ -166,3 +166,51 @@ with DevTools, or follow [verification](../../guides/verify-consent.md) in your
166
166
  Use [custom integrations](../../integrations/building-integrations.md) for an
167
167
  unlisted vendor. Google helpers have a separate
168
168
  [Consent Mode contract](../../integrations/google-tag-manager.md).
169
+
170
+ ## Granular consent
171
+
172
+ A visitor can grant marketing and still turn one vendor off. Declare the
173
+ vendors next to the scripts in `lib/scripts.ts`, with the `vendor` slug each
174
+ script already carries, and pass both to `ConsentRoot`:
175
+
176
+ ```ts title="lib/scripts.ts"
177
+ import type { Vendor } from 'c15t';
178
+
179
+ export const vendors: Vendor[] = [
180
+ {
181
+ id: 'x-pixel',
182
+ name: 'X Pixel',
183
+ category: 'marketing',
184
+ privacyPolicyUrl: 'https://x.com/privacy',
185
+ },
186
+ ];
187
+ ```
188
+
189
+ ```tsx title="components/consent.tsx"
190
+ import { scripts, vendors } from '../lib/scripts';
191
+
192
+ <ConsentRoot state={state} config={consentConfig} scripts={scripts} vendors={vendors}>
193
+ {children}
194
+ </ConsentRoot>
195
+ ```
196
+
197
+ The preference center lists each vendor under its category with a switch.
198
+ Integrations from `@c15t/scripts` set `vendor` to their manifest slug, so
199
+ `xPixel()` needs no extra wiring; give a hand-written script the same slug
200
+ in its `vendor` field. A backend manifest can declare vendors too. See
201
+ [granular consent](../../integrations/granular-consent.md) for storage,
202
+ bulk actions and the hooks a custom control uses.
203
+
204
+ ## Clear stored tracking data
205
+
206
+ Script gating does not remove cookies or Web Storage entries that a script
207
+ already wrote. Add `clearOnRevocation` to your `ConsentRoot` or provider
208
+ options to remove declared data when its category is denied. See
209
+ [clear on revocation](../../integrations/clear-on-revocation.md) for configuration
210
+ and browser limits.
211
+
212
+ ## Shared lifecycle controls
213
+
214
+ See [shared consent controls](../../guides/shared-consent-controls.md) for external CMPs,
215
+ preference delegation, withdrawal reloads, and application events. These controls
216
+ use the same core runtime across frameworks.
@@ -53,3 +53,17 @@ Use [custom integrations](../../integrations/building-integrations.md) for an
53
53
  unlisted vendor and [verification](../../guides/verify-consent.md) for the network
54
54
  checks. Google helpers have a separate
55
55
  [Consent Mode contract](../../integrations/google-tag-manager.md).
56
+
57
+ ## Clear stored tracking data
58
+
59
+ Script gating does not remove cookies or Web Storage entries that a script
60
+ already wrote. Add `clearOnRevocation` to `ConsentProvider.options` to remove
61
+ declared data when its category is denied. See
62
+ [clear on revocation](../../integrations/clear-on-revocation.md) for configuration
63
+ and browser limits.
64
+
65
+ ## Shared lifecycle controls
66
+
67
+ See [shared consent controls](../../guides/shared-consent-controls.md) for external CMPs,
68
+ preference delegation, withdrawal reloads, and application events. These controls
69
+ use the same core runtime across frameworks.