@c15t/react 3.0.0-alpha.5 → 3.0.0-alpha.7
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.
- package/AGENTS.md +6 -4
- package/SKILL.md +1 -1
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/components/iab-panel/atoms/vendor-list.js +1 -1
- package/dist/components/iab-panel/hooks/use-gvl-data.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/iab-prompt/atoms/overlay.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -1
- package/dist/components/panel-trigger/panel-trigger.js +1 -1
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/shared/ui/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/components/shared/ui/switch/switch.js +1 -1
- package/dist/components/shared/ui/tabs/tabs.js +1 -1
- package/dist/devtools-launcher.js +2 -0
- package/dist/devtools.js +1 -1
- package/dist/hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/hooks/use-late-entry.js +2 -0
- package/dist/iab/styles.tw3.css +2 -2
- package/dist/index-parts/core.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/styles.tw3.css +29 -5
- package/dist/version.js +1 -1
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +4 -4
- package/dist-types/components/iab-panel/index.d.ts +0 -1
- package/dist-types/components/panel/index.d.ts +0 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +5 -0
- package/dist-types/components/panel-trigger/panel-trigger.d.ts +0 -8
- package/dist-types/components/preferences/index.d.ts +0 -1
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +0 -1
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +0 -1
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +0 -1
- package/dist-types/components/shared/ui/switch/switch.d.ts +0 -1
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +0 -1
- package/dist-types/devtools-launcher.d.ts +29 -0
- package/dist-types/hooks/use-iab-manager.d.ts +4 -4
- package/dist-types/hooks/use-late-entry.d.ts +14 -0
- package/dist-types/hooks.d.ts +27 -5
- package/dist-types/index-parts/core.d.ts +1 -1
- package/dist-types/index.d.ts +1 -1
- package/dist-types/provider.d.ts +23 -4
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +6 -4
- package/docs/concepts/choose-your-setup.md +23 -6
- package/docs/concepts/consent-state.md +47 -0
- package/docs/concepts/data-fetching.md +68 -11
- package/docs/customization/motion.md +22 -13
- package/docs/customization/recipes.md +4 -5
- package/docs/customization/slots.md +3 -2
- package/docs/customization/stylesheets.md +29 -39
- package/docs/customization/tailwind.md +5 -5
- package/docs/customization/tokens.md +2 -1
- package/docs/customization/translations.md +32 -5
- package/docs/frameworks/react/components/consent-dialog-trigger.md +5 -0
- package/docs/frameworks/react/components/consent-dialog.md +2 -1
- package/docs/frameworks/react/components/consent-provider.md +29 -28
- package/docs/frameworks/react/components/dev-tools.md +23 -0
- package/docs/frameworks/react/headless.md +4 -4
- package/docs/frameworks/react/hooks.md +10 -5
- package/docs/frameworks/react/iab.md +24 -18
- package/docs/frameworks/react/quickstart.md +77 -16
- package/docs/frameworks/react/rendering.md +82 -11
- package/docs/frameworks/react/scripts.md +8 -3
- package/docs/frameworks/react/upgrade-v3.md +596 -0
- package/docs/frameworks/react/vendor-consent.md +6 -0
- package/docs/guides/banner-experiments.md +26 -5
- package/docs/guides/troubleshooting.md +3 -2
- package/docs/integrations/overview.md +28 -27
- package/docs/integrations/sentry.md +416 -0
- package/docs/upgrade-v3.md +205 -314
- package/package.json +8 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Stylesheets and CSS layers
|
|
3
|
-
description: Load the right c15t stylesheet for your framework, see
|
|
4
|
-
dialog's
|
|
5
|
-
without its styles.
|
|
3
|
+
description: Load the right c15t stylesheet for your framework, see which file
|
|
4
|
+
holds the dialog's rules, order c15t's cascade layer against your own, and run
|
|
5
|
+
c15t without its styles.
|
|
6
6
|
group: customization
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -26,32 +26,25 @@ when the integration sets `iab`. Tailwind 3 uses the same files and adds a
|
|
|
26
26
|
PostCSS plugin; [Tailwind CSS](./tailwind.md#set-up-tailwind-css-3)
|
|
27
27
|
shows the setup.
|
|
28
28
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
| Framework | The preference dialog's rules
|
|
37
|
-
| ------------------------------ |
|
|
38
|
-
| Next.js, TanStack Start, React |
|
|
39
|
-
| Nuxt, Vue | The dialog component imports its stylesheet, so the rules load with the dialog's code, not the banner's
|
|
40
|
-
| Astro | c15t
|
|
41
|
-
| Svelte, SvelteKit |
|
|
42
|
-
| HTML, JavaScript | Part of the stylesheet in the shadow root, which holds only the rules the stock surfaces use
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`@c15t/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
These modules import the stylesheet through `@c15t/ui/styles/dialog`. Under
|
|
50
|
-
the `node` export condition that module imports nothing, so server code that
|
|
51
|
-
loads `@c15t/react` with plain Node, such as the Pages Router or an SSR build
|
|
52
|
-
that keeps dependencies external, does not fail on the `.css` file. In your own
|
|
53
|
-
components, import `@c15t/ui/styles/dialog` rather than the `.css` file if the
|
|
54
|
-
module can run on the server.
|
|
29
|
+
## Where the dialog's rules come from
|
|
30
|
+
|
|
31
|
+
`styles.css` holds the default tokens, every c15t CSS variable and the rules
|
|
32
|
+
for every stock surface: the banner, the floating trigger, the `ConsentGate`
|
|
33
|
+
placeholder, the preference dialog and the preference widget. Only the IAB TCF
|
|
34
|
+
surfaces need a second file, `iab/styles.css`.
|
|
35
|
+
|
|
36
|
+
| Framework | The preference dialog's rules |
|
|
37
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| Next.js, TanStack Start, React | In `styles.css`. No `@c15t/react` or `@c15t/ui` module imports CSS, so a Next.js app that uses only the Pages Router builds without `transpilePackages` |
|
|
39
|
+
| Nuxt, Vue | The dialog component imports its stylesheet, so the rules load with the dialog's code, not the banner's |
|
|
40
|
+
| Astro | In `c15t/astro/styles.css`. The Svelte dialog, the default `ui`, also needs the primitives stylesheet, which c15t links when a visitor first points at, focuses or opens a control that opens the dialog, and keeps across `ClientRouter` navigation |
|
|
41
|
+
| Svelte, SvelteKit | In `@c15t/svelte/styles.css` |
|
|
42
|
+
| HTML, JavaScript | Part of the stylesheet in the shadow root, which holds only the rules the stock surfaces use |
|
|
43
|
+
|
|
44
|
+
Earlier 3.0 alphas shipped the dialog's rules in `@c15t/ui/styles/dialog.css`,
|
|
45
|
+
which the dialog's code imported. That file, the `@c15t/ui/styles/dialog`
|
|
46
|
+
module and `c15t/astro/dialog.css` are now empty and stay only so existing
|
|
47
|
+
imports keep resolving. Remove those imports.
|
|
55
48
|
|
|
56
49
|
## Order c15t's layer against your CSS
|
|
57
50
|
|
|
@@ -78,11 +71,9 @@ What this means for your overrides:
|
|
|
78
71
|
[theme tokens](./tokens.md).
|
|
79
72
|
|
|
80
73
|
If your CSS declares its own layer order, load that statement before c15t's
|
|
81
|
-
stylesheet.
|
|
82
|
-
`@import 'c15t/react/styles.css' layer(c15t)
|
|
83
|
-
|
|
84
|
-
for example `@layer c15t, components, app;`, so it does not land after your
|
|
85
|
-
own layers.
|
|
74
|
+
stylesheet. You can import c15t's stylesheet into a named layer, such as
|
|
75
|
+
`@import 'c15t/react/styles.css' layer(c15t)`. Its component rules then sit
|
|
76
|
+
in `c15t.components`, so order `c15t` against your own layers.
|
|
86
77
|
|
|
87
78
|
## Load Astro's stylesheets yourself
|
|
88
79
|
|
|
@@ -90,12 +81,11 @@ The Astro integration adds `c15t/astro/styles.css` to every page ahead of your
|
|
|
90
81
|
own CSS. To control the order yourself, for example from a global stylesheet
|
|
91
82
|
that names its own layers, set `styles: false` in the `c15t()` options and
|
|
92
83
|
import the files after your layer order statement. With `styles: false`, c15t
|
|
93
|
-
links no
|
|
94
|
-
|
|
84
|
+
links no stylesheet when the dialog opens either. The Svelte dialog, the
|
|
85
|
+
default `ui`, needs the primitives stylesheet next to `styles.css`:
|
|
95
86
|
|
|
96
87
|
```css title="src/styles/global.css"
|
|
97
88
|
@import 'c15t/astro/styles.css';
|
|
98
|
-
@import 'c15t/astro/dialog.css';
|
|
99
89
|
/* Only with ui: 'svelte'. */
|
|
100
90
|
@import 'c15t/astro/primitives.css';
|
|
101
91
|
/* Only when the integration sets iab. */
|
|
@@ -141,7 +131,7 @@ selector before you reach for `noStyle`.
|
|
|
141
131
|
|
|
142
132
|
1. Open DevTools Network, clear site data and reload. Your framework's c15t
|
|
143
133
|
stylesheet loads once, and no second copy appears.
|
|
144
|
-
2. Open the preference dialog. It is styled on its first frame,
|
|
145
|
-
|
|
134
|
+
2. Open the preference dialog. It is styled on its first frame, and in React,
|
|
135
|
+
Next.js and TanStack Start no new stylesheet request appears.
|
|
146
136
|
3. Select a banner part and read the Styles panel. c15t's rules appear under
|
|
147
137
|
`@layer components`, and your unlayered rules for the same property win.
|
|
@@ -282,8 +282,8 @@ Make three changes:
|
|
|
282
282
|
3. Set `darkMode: 'class'` so Tailwind's `dark:` variant follows the same
|
|
283
283
|
`dark` class c15t reads.
|
|
284
284
|
|
|
285
|
-
The plugin also processes the stylesheets c15t loads for you, such as
|
|
286
|
-
|
|
285
|
+
The plugin also processes the stylesheets c15t loads for you, such as Astro's
|
|
286
|
+
injected one. Without it, Tailwind 3 stops
|
|
287
287
|
the build with "`@layer components` is used but no matching
|
|
288
288
|
`@tailwind components` directive is present", or purges c15t's rules, because
|
|
289
289
|
c15t's class names never appear in your source.
|
|
@@ -300,7 +300,7 @@ cannot edit your PostCSS config, it prints the step to do by hand. See
|
|
|
300
300
|
[setup](https://c15t.com/docs/cli/commands/setup#set-up-styles-and-tailwind-css).
|
|
301
301
|
|
|
302
302
|
Create React App ignores PostCSS config files, so Tailwind 3 cannot run the
|
|
303
|
-
plugin and the build fails on c15t's
|
|
303
|
+
plugin and the build fails on c15t's stylesheet. `c15t setup` warns
|
|
304
304
|
about this. Add the plugin before `tailwindcss` through CRACO, eject, or move
|
|
305
305
|
the app to Vite.
|
|
306
306
|
|
|
@@ -771,7 +771,7 @@ stylesheet, and the layer order keeps it above preflight:
|
|
|
771
771
|
|
|
772
772
|
```html title="light-dom.html"
|
|
773
773
|
<script
|
|
774
|
-
src="/c15t.js"
|
|
774
|
+
src="/c15t.offline.js"
|
|
775
775
|
data-mode="offline"
|
|
776
776
|
data-country="DE"
|
|
777
777
|
data-shadow="false"
|
|
@@ -793,7 +793,7 @@ instead:
|
|
|
793
793
|
window.c15t = [['config', { ui: { shadow: false, styles: false } }]];
|
|
794
794
|
</script>
|
|
795
795
|
<script
|
|
796
|
-
src="/c15t.js"
|
|
796
|
+
src="/c15t.offline.js"
|
|
797
797
|
data-mode="offline"
|
|
798
798
|
data-country="DE"
|
|
799
799
|
defer
|
|
@@ -101,7 +101,7 @@ easing tokens.
|
|
|
101
101
|
| `--c15t-text-on-primary` | `colors.textOnPrimary` | auto-derived from `colors.primary` when omitted |
|
|
102
102
|
| `--c15t-overlay` | `colors.overlay` | `hsla(0, 0%, 0%, 0.5)` |
|
|
103
103
|
| `--c15t-switch-track` | `colors.switchTrack` | `hsl(0, 0%, 85%)` |
|
|
104
|
-
| `--c15t-switch-track-active` | `colors.switchTrackActive` | `
|
|
104
|
+
| `--c15t-switch-track-active` | `colors.switchTrackActive` | `colors.primary` |
|
|
105
105
|
| `--c15t-switch-thumb` | `colors.switchThumb` | `hsl(0, 0%, 100%)` |
|
|
106
106
|
| `--c15t-font-family` | `typography.fontFamily` | `system-ui, -apple-system, sans-serif` |
|
|
107
107
|
| `--c15t-font-size-sm` | `typography.fontSize.sm` | `0.875rem` |
|
|
@@ -130,6 +130,7 @@ easing tokens.
|
|
|
130
130
|
| `--c15t-duration-slow` | `motion.duration.slow` | `200ms` |
|
|
131
131
|
| `--c15t-easing` | `motion.easing` | `cubic-bezier(0.4, 0, 0.2, 1)` |
|
|
132
132
|
| `--c15t-easing-out` | `motion.easingOut` | `cubic-bezier(0.215, 0.61, 0.355, 1)` |
|
|
133
|
+
| `--c15t-easing-in` | `motion.easingIn` | `cubic-bezier(0.55, 0.055, 0.675, 0.19)` |
|
|
133
134
|
| `--c15t-easing-in-out` | `motion.easingInOut` | `cubic-bezier(0.645, 0.045, 0.355, 1)` |
|
|
134
135
|
| `--c15t-easing-spring` | `motion.easingSpring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
|
|
135
136
|
|
|
@@ -31,6 +31,33 @@ site-wide change. Astro's serializable integration options have their own
|
|
|
31
31
|
types, so verify that shape before copying a React object. The Vue plugin and
|
|
32
32
|
Nuxt module have no `i18n` option; their copy comes from the backend.
|
|
33
33
|
|
|
34
|
+
## Load only the languages you use
|
|
35
|
+
|
|
36
|
+
c15t bundles English. Every other language it ships is a separate entry point
|
|
37
|
+
in `@c15t/translations`, named by its language code. Importing one registers
|
|
38
|
+
c15t's built-in copy for that language. Offline mode can then switch to it,
|
|
39
|
+
and your messages that repeat its wording don't hide backend edits. Import
|
|
40
|
+
them in the file that configures the provider, so they load in the browser:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import '@c15t/translations/de';
|
|
44
|
+
import '@c15t/translations/fr';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each entry also exports that copy as `translations`, if you want to read a
|
|
48
|
+
string or build your messages from it:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { translations as de } from '@c15t/translations/de';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The entries are `bg`, `cs`, `cy`, `da`, `de`, `el`, `es`, `et`, `fi`, `fr`,
|
|
55
|
+
`ga`, `gu`, `he`, `hi`, `hr`, `hu`, `id`, `is`, `it`, `lb`, `lt`, `lv`, `mt`,
|
|
56
|
+
`nb`, `nl`, `nn`, `pl`, `pt`, `rm`, `ro`, `sk`, `sl`, `sv` and `zh`.
|
|
57
|
+
`@c15t/translations/all` still loads every language at once. The `c15t`
|
|
58
|
+
package doesn't re-export these entries, so add `@c15t/translations` to your
|
|
59
|
+
dependencies.
|
|
60
|
+
|
|
34
61
|
## Combine messages with backend copy
|
|
35
62
|
|
|
36
63
|
With a backend or a manifest, the copy it sends for the visitor's language is
|
|
@@ -41,11 +68,11 @@ its primary language, so `de-AT` uses your `de` messages when there is no
|
|
|
41
68
|
`de-AT` entry. Messages for other languages are not applied.
|
|
42
69
|
|
|
43
70
|
A key only overrides the backend when your text differs from c15t's built-in
|
|
44
|
-
wording for that language. So passing
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
wording for that language. So passing a stock bundle to enable a language
|
|
72
|
+
keeps backend edits visible, while a key you actually reworded stays pinned
|
|
73
|
+
in code. c15t only recognizes the built-in wording of English and of the
|
|
74
|
+
languages you [import](#load-only-the-languages-you-use), such as
|
|
75
|
+
`@c15t/translations/de` or `@c15t/translations/all`.
|
|
49
76
|
|
|
50
77
|
## Write labels that describe the action
|
|
51
78
|
|
|
@@ -112,6 +112,11 @@ nothing only when it has no visible item.
|
|
|
112
112
|
| `ariaLabel` | `string` | `'Privacy controls'` | Accessible name for the toolbar group. |
|
|
113
113
|
| `noStyle` | `boolean` | `false` | Remove the default styling. |
|
|
114
114
|
|
|
115
|
+
While `<ConsentDevTools>` is mounted, the toolbar adds a DevTools button at
|
|
116
|
+
the end farthest from its corner and opens the DevTools panel beside itself.
|
|
117
|
+
The ready-made `ConsentDialogTrigger` switches to this toolbar layout for as
|
|
118
|
+
long as DevTools is mounted.
|
|
119
|
+
|
|
115
120
|
### Toolbar styling
|
|
116
121
|
|
|
117
122
|
The toolbar follows the standard precedence: bundled styles, then provider slots, then direct `className` and `style` on the toolbar or on one action. Use `noStyle` for a fully custom implementation.
|
|
@@ -132,7 +132,8 @@ cached: the next hover, focus or open tries the download again.
|
|
|
132
132
|
The dialog's CSS travels in the same chunk and is applied before the dialog
|
|
133
133
|
renders, so your app's `styles.css` import stays small.
|
|
134
134
|
|
|
135
|
-
Without an `open` prop, Escape closes the dialog
|
|
135
|
+
Without an `open` prop, Escape closes the dialog without saving. If the
|
|
136
|
+
policy still owes a choice, the banner comes back. Save Settings, Accept All and
|
|
136
137
|
Reject All close it as soon as the choice is recorded in the browser, in
|
|
137
138
|
the same task as the click; the exit animation still plays. The request to
|
|
138
139
|
the backend runs afterwards and never reopens the dialog: a failed request
|
|
@@ -56,34 +56,35 @@ the provider to change them. `scripts`, `vendors`, `networkBlocker`,
|
|
|
56
56
|
`iframeBlocker`, `consentCategories`, `overrides`, `user`, `callbacks` and
|
|
57
57
|
`enabled` apply when they change.
|
|
58
58
|
|
|
59
|
-
| Option | Purpose
|
|
60
|
-
| ----------------------------- |
|
|
61
|
-
| `mode` | Required. `hosted({ url })` for a backend, `offline()` for local policies, or `custom()` for your own transport
|
|
62
|
-
| `prefetch` | Server-resolved state, or a promise of it, from a framework adapter's server helper
|
|
63
|
-
| `scripts` | Consent-gated vendor scripts. See [scripts and embeds](../scripts.md)
|
|
64
|
-
| `vendors` | Vendors listed under their category with their own switch. See [vendor consent](../vendor-consent.md)
|
|
65
|
-
| `scriptLoader` | Options for the script loader module
|
|
66
|
-
| `networkBlocker` | Rules that hold `fetch` and `XMLHttpRequest` calls until their category is allowed, or `false`
|
|
67
|
-
| `iframeBlocker` | Gates DOM iframes that carry `data-category`. On by default; `false` turns it off
|
|
68
|
-
| `persistence` | Stores the visitor's choice in a cookie and localStorage. On by default
|
|
69
|
-
| `storageConfig` | Storage key and cookie settings for the stored choice
|
|
70
|
-
| `clearOnRevocation` | Cookies and storage keys to delete when a category is denied
|
|
71
|
-
| `reloadOnConsentRevoked` | Reload the page after the visitor turns off a granted category or vendor. Defaults to `true`
|
|
72
|
-
| `consentCategories` | Categories the preference dialog offers alongside those your scripts use, within the policy's scope
|
|
73
|
-
| `overrides` | Force the country, region or language the policy is resolved for, for testing
|
|
74
|
-
| `user` | Link consent records to a signed-in user
|
|
75
|
-
| `callbacks` | Choice, permission and error events
|
|
76
|
-
| `i18n` | Languages and message overrides
|
|
77
|
-
| `legalLinks` | Privacy policy and other links shown in the banner and dialog
|
|
78
|
-
| `theme` | `consentActions` and slot styles. Tokens go through `ConsentTheme`; see [customize](../customize.md)
|
|
79
|
-
| `components` | Slot attributes per component part
|
|
80
|
-
| `presentation` | Banner and dialog shape, position and blocking
|
|
81
|
-
| `experiment` | A/B test of `presentation`, read once at mount. See [banner experiments](../../../guides/banner-experiments.md)
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
59
|
+
| Option | Purpose |
|
|
60
|
+
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `mode` | Required. `hosted({ url })` for a backend, `offline()` for local policies, or `custom()` for your own transport |
|
|
62
|
+
| `prefetch` | Server-resolved state, or a promise of it, from a framework adapter's server helper |
|
|
63
|
+
| `scripts` | Consent-gated vendor scripts. See [scripts and embeds](../scripts.md) |
|
|
64
|
+
| `vendors` | Vendors listed under their category with their own switch. See [vendor consent](../vendor-consent.md) |
|
|
65
|
+
| `scriptLoader` | Options for the script loader module |
|
|
66
|
+
| `networkBlocker` | Rules that hold `fetch` and `XMLHttpRequest` calls until their category is allowed, or `false` |
|
|
67
|
+
| `iframeBlocker` | Gates DOM iframes that carry `data-category`. On by default; `false` turns it off |
|
|
68
|
+
| `persistence` | Stores the visitor's choice in a cookie and localStorage. On by default |
|
|
69
|
+
| `storageConfig` | Storage key and cookie settings for the stored choice |
|
|
70
|
+
| `clearOnRevocation` | Cookies and storage keys to delete when a category is denied |
|
|
71
|
+
| `reloadOnConsentRevoked` | Reload the page after the visitor turns off a granted category or vendor. Defaults to `true` |
|
|
72
|
+
| `consentCategories` | Categories the preference dialog offers alongside those your scripts use, within the policy's scope |
|
|
73
|
+
| `overrides` | Force the country, region or language the policy is resolved for, for testing |
|
|
74
|
+
| `user` | Link consent records to a signed-in user |
|
|
75
|
+
| `callbacks` | Choice, permission and error events |
|
|
76
|
+
| `i18n` | Languages and message overrides |
|
|
77
|
+
| `legalLinks` | Privacy policy and other links shown in the banner and dialog |
|
|
78
|
+
| `theme` | `consentActions` and slot styles. Tokens go through `ConsentTheme`; see [customize](../customize.md) |
|
|
79
|
+
| `components` | Slot attributes per component part |
|
|
80
|
+
| `presentation` | Banner and dialog shape, position and blocking |
|
|
81
|
+
| `experiment` | A/B test of `presentation`, read once at mount. See [banner experiments](../../../guides/banner-experiments.md) |
|
|
82
|
+
| `journey` | Random id that links each `/init` to the save that follows. See [session reports](../../../concepts/data-fetching.md#link-an-init-to-the-save-that-follows) |
|
|
83
|
+
| `colorScheme` | `'light'`, `'dark'`, `'system'`, or `null` to leave the `c15t-dark` class alone |
|
|
84
|
+
| `noStyle`, `disableAnimation` | Drop the built-in styles, or turn off animations |
|
|
85
|
+
| `preloadDialog` | When the deferred `ConsentDialog` starts loading: `'idle'` (default) or `'intent'`; see [ConsentDialog](./consent-dialog.md#behavior) |
|
|
86
|
+
| `nonce` | Content Security Policy nonce for script elements c15t inserts |
|
|
87
|
+
| `enabled` | Set `false` only when you deliberately bypass consent enforcement |
|
|
87
88
|
|
|
88
89
|
`enabled: false` grants categories and allows gated loading while suppressing
|
|
89
90
|
consent UI. It is not a way to fix failed initialization or hide a banner in
|
|
@@ -76,6 +76,29 @@ panel renders inside a shadow root by default, so page CSS does not reach
|
|
|
76
76
|
it. `shadow={false}` mounts it in the light DOM with its stylesheet in
|
|
77
77
|
`<head>`.
|
|
78
78
|
|
|
79
|
+
## Open DevTools from the consent trigger
|
|
80
|
+
|
|
81
|
+
While `ConsentDialogTriggerToolbar` or the ready-made `ConsentDialogTrigger`
|
|
82
|
+
is visible, the trigger shows a DevTools button and DevTools hides its own
|
|
83
|
+
floating launcher, so one control occupies the corner. The button sits at the
|
|
84
|
+
end of the toolbar farthest from its corner. The panel opens just past the
|
|
85
|
+
toolbar, aligned with its outer edge, and follows it when a visitor drags the
|
|
86
|
+
toolbar to another corner. `position` applies again once no trigger is
|
|
87
|
+
visible.
|
|
88
|
+
|
|
89
|
+
`ConsentDialogTrigger` becomes a two-button toolbar while DevTools is
|
|
90
|
+
mounted. A trigger composed from `ConsentDialogTrigger.Root` keeps its own
|
|
91
|
+
markup, and DevTools keeps its floating launcher beside it.
|
|
92
|
+
|
|
93
|
+
DevTools brings its launcher back whenever no trigger is visible, for example
|
|
94
|
+
while `showWhen="after-prompt"` waits for a choice. A production build that
|
|
95
|
+
loads DevTools only in development shows no DevTools button in the trigger.
|
|
96
|
+
|
|
97
|
+
With the imperative engine, call `devTools.dock({ position, inline, block })`
|
|
98
|
+
to hide the launcher and anchor the panel `inline` and `block` pixels from the
|
|
99
|
+
viewport edges of `position`'s corner. Call `devTools.dock(null)` to restore
|
|
100
|
+
the launcher.
|
|
101
|
+
|
|
79
102
|
## Panels
|
|
80
103
|
|
|
81
104
|
Accept and reject apply only to categories displayed by the provider, leaving
|
|
@@ -34,13 +34,13 @@ import { useHeadlessConsentUI, useTranslations } from 'c15t/react/headless';
|
|
|
34
34
|
|
|
35
35
|
const ACTION_LABELS = {
|
|
36
36
|
accept: 'acceptAll',
|
|
37
|
-
reject: 'rejectAll',
|
|
38
37
|
customize: 'customize',
|
|
39
38
|
dismiss: 'acknowledge',
|
|
39
|
+
reject: 'rejectAll',
|
|
40
40
|
save: 'save',
|
|
41
41
|
} as const;
|
|
42
42
|
|
|
43
|
-
export
|
|
43
|
+
export const Banner = () => {
|
|
44
44
|
const { banner, performAction, openDialog } = useHeadlessConsentUI();
|
|
45
45
|
const { common, rights } = useTranslations();
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ export function Banner() {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
|
-
<section
|
|
52
|
+
<section aria-label="Privacy">
|
|
53
53
|
{banner.preferenceControls.map((right) => (
|
|
54
54
|
<button key={right} type="button" onClick={openDialog}>
|
|
55
55
|
{right === 'opt-out' ? rights?.optOut : rights?.preferences}
|
|
@@ -71,7 +71,7 @@ export function Banner() {
|
|
|
71
71
|
))}
|
|
72
72
|
</section>
|
|
73
73
|
);
|
|
74
|
-
}
|
|
74
|
+
};
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
`banner.actionGroups` is the resolved layout: reject and accept share a group
|
|
@@ -14,10 +14,10 @@ from components rendered inside that provider and import them from
|
|
|
14
14
|
```tsx title="src/optional-feature.tsx"
|
|
15
15
|
import { useConsent } from 'c15t/react';
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export const OptionalFeature = () => {
|
|
18
18
|
const allowed = useConsent('marketing');
|
|
19
19
|
return allowed ? <div>Optional marketing content</div> : null;
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Call hooks inside the consent provider. `useConsent` reads effective permission,
|
|
@@ -42,14 +42,19 @@ Render this component inside your existing `ConsentProvider`.
|
|
|
42
42
|
```tsx title="src/reject-button.tsx"
|
|
43
43
|
import { useSaveConsents } from 'c15t/react';
|
|
44
44
|
|
|
45
|
-
export
|
|
45
|
+
export const RejectButton = () => {
|
|
46
46
|
const save = useSaveConsents();
|
|
47
47
|
return (
|
|
48
|
-
<button
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
void save('none');
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
49
54
|
Reject optional
|
|
50
55
|
</button>
|
|
51
56
|
);
|
|
52
|
-
}
|
|
57
|
+
};
|
|
53
58
|
```
|
|
54
59
|
|
|
55
60
|
This is an isolated action example, not a complete policy-aware banner. Use the
|
|
@@ -33,14 +33,12 @@ import {
|
|
|
33
33
|
IABConsentDialog,
|
|
34
34
|
} from 'c15t/react/iab';
|
|
35
35
|
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
43
|
-
}
|
|
36
|
+
export const IABConsent = ({ cmpId }: { cmpId: number }) => (
|
|
37
|
+
<IABProvider cmpId={cmpId}>
|
|
38
|
+
<IABConsentBanner />
|
|
39
|
+
<IABConsentDialog />
|
|
40
|
+
</IABProvider>
|
|
41
|
+
);
|
|
44
42
|
```
|
|
45
43
|
|
|
46
44
|
Supply the CMP ID from your deployment's IAB configuration. Keep a persistent
|
|
@@ -85,14 +83,12 @@ const publisherRestrictions = [
|
|
|
85
83
|
{ purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
|
|
86
84
|
];
|
|
87
85
|
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
}
|
|
86
|
+
export const IABConsent = ({ cmpId }: { cmpId: number }) => (
|
|
87
|
+
<IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
|
|
88
|
+
<IABConsentBanner />
|
|
89
|
+
<IABConsentDialog />
|
|
90
|
+
</IABProvider>
|
|
91
|
+
);
|
|
96
92
|
```
|
|
97
93
|
|
|
98
94
|
Each restriction type has a requirement that depends on the vendor list:
|
|
@@ -154,8 +150,10 @@ because turning one off would change nothing the vendors rely on; its
|
|
|
154
150
|
objection control is the opt-out. Such a purpose does not decide its c15t
|
|
155
151
|
category: the category follows the purposes in it that the visitor can
|
|
156
152
|
consent to. Legitimate interest never grants a category on its own. If none of
|
|
157
|
-
a category's purposes has a consent basis,
|
|
158
|
-
|
|
153
|
+
a category's purposes has a consent basis, nothing can consent to them, Accept
|
|
154
|
+
All included, so the category stays denied. See
|
|
155
|
+
[categories under an IAB policy](../../concepts/consent-state.md#categories-under-an-iab-policy).
|
|
156
|
+
Stack switches
|
|
159
157
|
cover only the purposes some vendor processes on consent. Display-model rows
|
|
160
158
|
expose this as `hasConsentBasis`. Custom preference UIs can call
|
|
161
159
|
`applyPublisherRestrictionsToGVL` from `@c15t/iab/headless`, or pass
|
|
@@ -209,6 +207,14 @@ the special purposes only. `featureRows` holds the features, with
|
|
|
209
207
|
`featuresStandardText` is the standard text, or `null` when you should use the
|
|
210
208
|
translation.
|
|
211
209
|
|
|
210
|
+
Each vendor's privacy policy and legitimate interest links come from its
|
|
211
|
+
`urls[]` entry in the Global Vendor List. The stock preference centres pick
|
|
212
|
+
the entry for the language the banner shows, then English. A custom UI gets
|
|
213
|
+
the same links from `resolveIABVendorUrls(vendor, language)` in
|
|
214
|
+
`@c15t/iab/headless`, or from `policyUrl` and `legitimateInterestUrl` on the
|
|
215
|
+
vendors `processGVLForDialog` returns when you pass it `language`. Vendor lists
|
|
216
|
+
older than GVL v3 have a single `policyUrl` field, which c15t still reads.
|
|
217
|
+
|
|
212
218
|
## Consent scope
|
|
213
219
|
|
|
214
220
|
c15t stores TCF choices per site and browser, which the TCF calls
|
|
@@ -8,8 +8,9 @@ group: frameworks
|
|
|
8
8
|
## Before you start
|
|
9
9
|
|
|
10
10
|
This guide sets up a client-rendered React app built with Vite. The browser
|
|
11
|
-
loads the page,
|
|
12
|
-
the
|
|
11
|
+
loads the page, resolves the visitor's policy, then shows the banner or runs
|
|
12
|
+
the scripts the visitor allowed. The build-time step bundles the public
|
|
13
|
+
policy; regional policies may still need a backend location request.
|
|
13
14
|
|
|
14
15
|
* **Next.js or TanStack Start?** Use that framework's guide from the
|
|
15
16
|
[framework list](https://c15t.com/docs/frameworks). Their adapters resolve consent on the
|
|
@@ -26,6 +27,10 @@ the backend URL.
|
|
|
26
27
|
[Choose your setup](../../concepts/choose-your-setup.md#decide-who-runs-the-consent-backend)
|
|
27
28
|
covers self-hosting and offline mode.
|
|
28
29
|
|
|
30
|
+
Build-time manifests are the recommended production setup. Complete the
|
|
31
|
+
[build-time manifest step](#bundle-the-manifest-during-builds) before
|
|
32
|
+
deploying, and rebuild after changing policies, translations or vendors.
|
|
33
|
+
|
|
29
34
|
## Install c15t
|
|
30
35
|
|
|
31
36
|
| Package manager | Command |
|
|
@@ -40,14 +45,12 @@ vendor loaders used below.
|
|
|
40
45
|
|
|
41
46
|
## Register your vendor scripts
|
|
42
47
|
|
|
43
|
-
Create `src/scripts.ts`. PostHog waits for measurement permission
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
once.
|
|
48
|
+
Create `src/scripts.ts`. PostHog waits for measurement permission, so your
|
|
49
|
+
policy must include that category. Remove any other PostHog loader from your
|
|
50
|
+
app so PostHog loads once.
|
|
47
51
|
|
|
48
52
|
```ts title="src/scripts.ts"
|
|
49
53
|
import { posthog } from '@c15t/integrations/posthog';
|
|
50
|
-
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
51
54
|
|
|
52
55
|
export const scripts = [
|
|
53
56
|
posthog({
|
|
@@ -55,13 +58,11 @@ export const scripts = [
|
|
|
55
58
|
initOptions: { cookieless_mode: 'never' },
|
|
56
59
|
loadMode: 'after-consent',
|
|
57
60
|
}),
|
|
58
|
-
xPixel({ pixelId: 'your-pixel-id' }),
|
|
59
61
|
];
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
Replace `phc_your_project_key` with your PostHog project key
|
|
63
|
-
|
|
64
|
-
`region: 'us'` for a US project. Remove the loader for a vendor you don't use.
|
|
64
|
+
Replace `phc_your_project_key` with your PostHog project key. PostHog uses its
|
|
65
|
+
EU host; add `region: 'us'` for a US project.
|
|
65
66
|
|
|
66
67
|
`loadMode: 'after-consent'` keeps the PostHog SDK off the page until the
|
|
67
68
|
visitor allows measurement. `cookieless_mode: 'never'` turns off PostHog's
|
|
@@ -130,6 +131,66 @@ router, render the router inside `Consent` so navigation does not remount the
|
|
|
130
131
|
provider. The stylesheet import in `src/consent.tsx` styles the banner and
|
|
131
132
|
dialog; import it once.
|
|
132
133
|
|
|
134
|
+
## Bundle the manifest during builds
|
|
135
|
+
|
|
136
|
+
The build fetches your public policy once and bundles it, so the server never
|
|
137
|
+
fetches it at runtime.
|
|
138
|
+
|
|
139
|
+
Add `consentManifest` to your existing Vite plugin list:
|
|
140
|
+
|
|
141
|
+
```ts title="vite.config.ts (plugins option)"
|
|
142
|
+
import { consentManifest } from 'c15t/build';
|
|
143
|
+
|
|
144
|
+
export default {
|
|
145
|
+
plugins: [
|
|
146
|
+
// Keep your existing framework plugins here.
|
|
147
|
+
consentManifest({ backendURL: 'https://your-project.inth.app' }),
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Vite writes `src/c15t-manifest.ts` before compiling, including at development
|
|
153
|
+
startup. Keep the generated file out of Git. Install `@c15t/browser@alpha` for
|
|
154
|
+
its browser manifest transport. In the quickstart's `src/consent.tsx`, replace
|
|
155
|
+
the `hosted()` mode with this factory:
|
|
156
|
+
|
|
157
|
+
```tsx title="src/consent.tsx"
|
|
158
|
+
import { manifest } from '@c15t/browser/headless';
|
|
159
|
+
|
|
160
|
+
import { consentManifest } from './c15t-manifest';
|
|
161
|
+
|
|
162
|
+
const mode = manifest({
|
|
163
|
+
backendURL: 'https://your-project.inth.app',
|
|
164
|
+
manifest: consentManifest,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Keep passing `mode` to `ConsentProvider`. The browser resolves the snapshot
|
|
169
|
+
without `/manifest` when it has the geography the policy needs, or the policy
|
|
170
|
+
does not depend on geography. Supply known country and region through `inputs`
|
|
171
|
+
or overrides to keep regional resolution on the snapshot.
|
|
172
|
+
|
|
173
|
+
When the required country or region is missing, the transport calls backend
|
|
174
|
+
`/init` and uses its resolved policy and translations. That response can use
|
|
175
|
+
newer backend policy than the build snapshot. This is a full consent resolution,
|
|
176
|
+
so these visitors still wait for a backend round trip.
|
|
177
|
+
|
|
178
|
+
Policy and resolver code are part of the browser bundle.
|
|
179
|
+
|
|
180
|
+
The snapshot is fixed at build time:
|
|
181
|
+
|
|
182
|
+
* Rebuild after changing policies, translations or vendors. If your CI caches
|
|
183
|
+
build output, force a fresh build.
|
|
184
|
+
* The build must reach the backend. A failed fetch fails the build.
|
|
185
|
+
* Consent choices still go to the backend.
|
|
186
|
+
|
|
187
|
+
Run `vite build` or `vite dev` before a standalone `tsc`, `vue-tsc` or
|
|
188
|
+
`svelte-check`; the generated file doesn't exist on a fresh checkout.
|
|
189
|
+
|
|
190
|
+
For policy updates without a rebuild, keep the original `hosted()` factory
|
|
191
|
+
and omit the build plugin. That setup resolves policy through the backend
|
|
192
|
+
`/init` on each page load.
|
|
193
|
+
|
|
133
194
|
## Gate your own features
|
|
134
195
|
|
|
135
196
|
Read a category's permission with `useConsent` inside the provider:
|
|
@@ -155,16 +216,16 @@ then open the site in a private window with DevTools open on the Network tab.
|
|
|
155
216
|
Test under a policy that asks for a choice, such as an EU opt-in policy.
|
|
156
217
|
|
|
157
218
|
1. **Before a choice.** The banner shows. There are no requests to
|
|
158
|
-
`posthog.com
|
|
219
|
+
`posthog.com`.
|
|
159
220
|
2. **Reject All.** The banner closes and the vendor requests stay absent.
|
|
160
221
|
3. **Reload.** The banner stays closed and the vendor requests stay absent.
|
|
161
222
|
4. **Privacy settings.** The footer link opens the dialog with **Analytics** (the
|
|
162
|
-
`measurement` category) and **Marketing** off. Turn on **Analytics** and
|
|
163
|
-
|
|
223
|
+
`measurement` category) and **Marketing** off. Turn on **Analytics** and
|
|
224
|
+
save. PostHog's `array.js` loads.
|
|
164
225
|
5. **Turn Analytics off again.** The page reloads and PostHog does not load.
|
|
165
226
|
|
|
166
|
-
If the banner never appears, check the
|
|
167
|
-
`/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
227
|
+
If the banner never appears, check the generated policy and any location
|
|
228
|
+
request to `/init` on your backend URL. [Troubleshooting](./troubleshooting.md)
|
|
168
229
|
covers the common causes. [Verify consent](../../guides/verify-consent.md) has the
|
|
169
230
|
full release checklist.
|
|
170
231
|
|