@c15t/react 3.0.0-alpha.3 → 3.0.0-alpha.5
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 +92 -72
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/iab-panel/atoms/card.js +1 -1
- package/dist/components/iab-panel/atoms/feature-item.js +2 -0
- package/dist/components/iab-panel/atoms/header.js +1 -1
- package/dist/components/iab-panel/iab-panel.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/atoms/card.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/components.js +1 -1
- package/dist/components/shared/libs/compose-refs.js +1 -0
- package/dist/components/shared/libs/slot.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft-context.js +2 -0
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/gpp.js +2 -0
- package/dist/hooks/use-color-scheme.js +1 -1
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +4 -4
- package/dist/index-parts/consent-banner.js +1 -0
- package/dist/index-parts/consent-dialog-link.js +1 -0
- package/dist/index-parts/consent-dialog-trigger.js +1 -0
- package/dist/index-parts/consent-dialog.js +1 -0
- package/dist/index-parts/consent-gate.js +1 -0
- package/dist/index-parts/core.js +1 -0
- package/dist/index-parts/draft.js +1 -0
- package/dist/index-parts/hooks.js +1 -0
- package/dist/index-parts/module-hooks.js +1 -0
- package/dist/index-parts/provider.js +1 -0
- package/dist/index-parts/theme.js +1 -0
- package/dist/index-parts/translations.js +1 -0
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -1
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/provider.js +1 -1
- package/dist/server/fetch-ssr-data.js +1 -1
- package/dist/server/headers.js +1 -1
- package/dist/server/normalize-url.js +1 -1
- package/dist/styles.tw3.css +16 -16
- package/dist/transports/offline.js +1 -1
- package/dist/utils/merge-slot-props.js +1 -1
- package/dist/utils/theme-utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
- package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +5 -2
- package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
- package/dist-types/components/iab-prompt/index.d.ts +2 -2
- package/dist-types/components/panel/atoms/card.d.ts +1 -0
- package/dist-types/components/prompt/components.d.ts +6 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/context/consent-tracking-context.d.ts +10 -0
- package/dist-types/context/theme-context.d.ts +2 -2
- package/dist-types/context.d.ts +6 -0
- package/dist-types/devtools.d.ts +13 -2
- package/dist-types/draft-context.d.ts +31 -0
- package/dist-types/draft.d.ts +13 -30
- package/dist-types/gpp.d.ts +27 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +57 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index-parts/consent-banner.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog.d.ts +7 -0
- package/dist-types/index-parts/consent-gate.d.ts +7 -0
- package/dist-types/index-parts/core.d.ts +8 -0
- package/dist-types/index-parts/draft.d.ts +7 -0
- package/dist-types/index-parts/hooks.d.ts +7 -0
- package/dist-types/index-parts/module-hooks.d.ts +7 -0
- package/dist-types/index-parts/provider.d.ts +7 -0
- package/dist-types/index-parts/theme.d.ts +8 -0
- package/dist-types/index-parts/translations.d.ts +7 -0
- package/dist-types/index.d.ts +14 -15
- package/dist-types/kernel-selector.d.ts +0 -8
- package/dist-types/module-hooks/iframe-blocker.d.ts +12 -7
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/provider.d.ts +57 -17
- package/dist-types/server/headers.d.ts +14 -4
- package/dist-types/server/normalize-url.d.ts +14 -7
- package/dist-types/server/types.d.ts +12 -1
- package/dist-types/transports/offline.d.ts +7 -16
- package/dist-types/ui-config-context.d.ts +7 -1
- package/dist-types/utils/merge-slot-props.d.ts +15 -1
- package/dist-types/utils/theme-utils.d.ts +17 -2
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +92 -72
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +87 -0
- package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
- package/docs/{guides → concepts}/consent-state.md +89 -105
- package/docs/{guides → concepts}/data-fetching.md +31 -27
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +119 -0
- package/docs/customization/overview.md +67 -34
- package/docs/customization/recipes.md +839 -49
- package/docs/customization/slots.md +216 -35
- package/docs/customization/stylesheets.md +147 -0
- package/docs/customization/tailwind.md +842 -0
- package/docs/customization/tokens.md +163 -96
- package/docs/customization/translations.md +60 -3
- package/docs/frameworks/react/callbacks.md +118 -0
- package/docs/frameworks/react/clear-on-revocation.md +106 -0
- package/docs/frameworks/react/components/consent-banner.md +42 -28
- package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
- package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
- package/docs/frameworks/react/components/consent-dialog.md +56 -50
- package/docs/frameworks/react/components/consent-gate.md +37 -20
- package/docs/frameworks/react/components/consent-provider.md +130 -0
- package/docs/frameworks/react/components/consent-widget.md +23 -20
- package/docs/frameworks/react/components/dev-tools.md +53 -85
- package/docs/frameworks/react/components.md +105 -0
- package/docs/frameworks/react/compose.md +354 -0
- package/docs/frameworks/react/content-security-policy.md +126 -0
- package/docs/frameworks/react/customize.md +250 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/gpp.md +163 -0
- package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
- package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
- package/docs/frameworks/react/network-blocker.md +35 -41
- package/docs/frameworks/react/quickstart.md +139 -101
- package/docs/frameworks/react/rendering.md +128 -0
- package/docs/frameworks/react/scripts.md +115 -0
- package/docs/frameworks/react/translations.md +155 -0
- package/docs/frameworks/react/troubleshooting.md +92 -47
- package/docs/frameworks/react/vendor-consent.md +170 -0
- package/docs/guides/banner-experiments.md +654 -0
- package/docs/guides/troubleshooting.md +120 -47
- package/docs/guides/verify-consent.md +81 -49
- package/docs/integrations/adobe-analytics.md +167 -159
- package/docs/integrations/ahrefs-analytics.md +152 -154
- package/docs/integrations/amplitude.md +162 -156
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +154 -154
- package/docs/integrations/cloudflare-web-analytics.md +156 -156
- package/docs/integrations/cloudflare-zaraz.md +209 -261
- package/docs/integrations/crisp.md +164 -158
- package/docs/integrations/databuddy.md +157 -173
- package/docs/integrations/fathom-analytics.md +158 -156
- package/docs/integrations/front-chat.md +167 -167
- package/docs/integrations/google-maps.md +118 -83
- package/docs/integrations/google-tag-manager.md +178 -163
- package/docs/integrations/google-tag.md +163 -160
- package/docs/integrations/heap.md +163 -155
- package/docs/integrations/hightouch.md +161 -157
- package/docs/integrations/hotjar.md +159 -155
- package/docs/integrations/intercom.md +183 -153
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +174 -150
- package/docs/integrations/logrocket.md +160 -156
- package/docs/integrations/matomo-analytics.md +188 -178
- package/docs/integrations/meta-pixel.md +188 -150
- package/docs/integrations/microsoft-clarity.md +163 -155
- package/docs/integrations/microsoft-uet.md +148 -154
- package/docs/integrations/mixpanel-analytics.md +155 -160
- package/docs/integrations/one-dollar-stats.md +166 -165
- package/docs/integrations/openai-pixel.md +204 -301
- package/docs/integrations/overview.md +137 -80
- package/docs/integrations/pinterest-tag.md +191 -183
- package/docs/integrations/pirsch.md +169 -159
- package/docs/integrations/plausible-analytics.md +172 -158
- package/docs/integrations/posthog.md +313 -240
- package/docs/integrations/promptwatch.md +154 -154
- package/docs/integrations/reddit-pixel.md +185 -157
- package/docs/integrations/rudderstack.md +201 -186
- package/docs/integrations/rybbit-analytics.md +171 -160
- package/docs/integrations/segment.md +182 -154
- package/docs/integrations/snapchat-pixel.md +186 -156
- package/docs/integrations/tiktok-pixel.md +171 -150
- package/docs/integrations/umami-analytics.md +163 -158
- package/docs/integrations/vercel-analytics.md +166 -157
- package/docs/integrations/x-pixel.md +176 -150
- package/docs/integrations/youtube.md +121 -86
- package/docs/upgrade-v3.md +496 -467
- package/package.json +20 -9
- package/dist/ui-save.js +0 -1
- package/dist-types/ui-save.d.ts +0 -25
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
- package/docs/frameworks/react/script-loader.md +0 -69
- package/docs/frameworks/react/styling/overview.md +0 -266
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/guides/shared-consent-controls.md +0 -158
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -210
|
@@ -1,88 +1,22 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Theme tokens
|
|
3
|
-
description:
|
|
4
|
-
|
|
2
|
+
title: Theme tokens
|
|
3
|
+
description: Change c15t's colors, type, radius, spacing, shadows and motion
|
|
4
|
+
with theme tokens, and see every --c15t-* variable with its default.
|
|
5
5
|
group: customization
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Where tokens come from
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
```tsx
|
|
21
|
-
import 'c15t/react/styles.css';
|
|
22
|
-
```
|
|
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
|
-
|
|
61
|
-
The standard stylesheet places rules in `@layer components`. For Tailwind 3,
|
|
62
|
-
use the `styles.tw3.css` entry instead of the standard stylesheet, between the
|
|
63
|
-
components and utilities directives in your Tailwind entry:
|
|
64
|
-
|
|
65
|
-
```css
|
|
66
|
-
@tailwind base;
|
|
67
|
-
@tailwind components;
|
|
68
|
-
@import 'c15t/react/styles.tw3.css';
|
|
69
|
-
@tailwind utilities;
|
|
70
|
-
```
|
|
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
|
-
|
|
84
|
-
Do not load both c15t stylesheet variants. For Tailwind 4 or unlayered CSS,
|
|
85
|
-
inspect layer order before reaching for `!important`.
|
|
10
|
+
c15t's components take their colors from `--c15t-*` CSS variables, and most
|
|
11
|
+
of their fonts, radii, spacing, shadows and motion too. c15t's stylesheet sets
|
|
12
|
+
the defaults. You change them with a theme object or with CSS, and every part
|
|
13
|
+
that reads a token changes with it. Some values are still fixed for one
|
|
14
|
+
component, such as button padding, the radius of the "Secured by" tag and
|
|
15
|
+
several font sizes and weights, so a token change does not move them. Restyle
|
|
16
|
+
those parts through [slots](./slots.md) or your own CSS.
|
|
17
|
+
[Stylesheets and CSS layers](./stylesheets.md)
|
|
18
|
+
covers which stylesheet to load, and [dark mode](./dark-mode.md)
|
|
19
|
+
covers the dark set of tokens.
|
|
86
20
|
|
|
87
21
|
## Set semantic values together
|
|
88
22
|
|
|
@@ -94,27 +28,117 @@ readable in each state.
|
|
|
94
28
|
import { defineTheme } from '@c15t/ui/theme';
|
|
95
29
|
|
|
96
30
|
export const theme = defineTheme({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
31
|
+
colors: { primary: '#2f6f4e' },
|
|
32
|
+
radius: { lg: '4px' },
|
|
33
|
+
consentActions: {
|
|
34
|
+
primary: { variant: 'primary', mode: 'filled' },
|
|
35
|
+
dismiss: { variant: 'neutral', mode: 'stroke' },
|
|
36
|
+
},
|
|
103
37
|
});
|
|
104
38
|
```
|
|
105
39
|
|
|
106
|
-
Install `@c15t/ui` if importing its theme helper directly.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
40
|
+
Install `@c15t/ui` if importing its theme helper directly. Where the theme
|
|
41
|
+
goes depends on the framework:
|
|
42
|
+
|
|
43
|
+
| Framework | Tokens | `consentActions` |
|
|
44
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
45
|
+
| Next.js, TanStack Start, React | `<ConsentTheme theme={theme} />`, rendered on the server where the app has one | `theme` in the provider options |
|
|
46
|
+
| Nuxt, Vue | `theme` or `tokens` in the module or plugin options | Not available |
|
|
47
|
+
| Astro | `theme` in the integration options | The same `theme` |
|
|
48
|
+
| Svelte, SvelteKit | `generateThemeCSS(theme)` from `@c15t/ui/theme` on the server, in a `<style>` element, or `--c15t-*` variables in your stylesheet | `theme` on `ConsentManagerProvider` |
|
|
49
|
+
| HTML, JavaScript | `ui.theme` | Not available |
|
|
50
|
+
|
|
51
|
+
React and Svelte providers do not turn tokens in their `theme` option into
|
|
52
|
+
CSS, and warn in development when a theme holds tokens but the page has no
|
|
53
|
+
`<style id="c15t-theme">`. Your framework's customize page shows the full
|
|
54
|
+
setup.
|
|
114
55
|
|
|
115
56
|
`consentActions` selects styling by action role. A per-action entry overrides
|
|
116
57
|
`primary`, which overrides `default`.
|
|
117
58
|
|
|
59
|
+
## Combine a generated theme with your own CSS
|
|
60
|
+
|
|
61
|
+
`generateThemeCSS` writes its variables on `:root:root` and
|
|
62
|
+
`.c15t-theme-root.c15t-theme-root`, one step more specific than the defaults
|
|
63
|
+
in `styles.css`. The theme therefore overrides the defaults whether its
|
|
64
|
+
`<style>` element comes before or after the stylesheet. The same output backs
|
|
65
|
+
`ConsentTheme` in React, Next.js and TanStack Start, Astro's `theme` option and
|
|
66
|
+
the script tag's `ui.theme`.
|
|
67
|
+
|
|
68
|
+
A `--c15t-*` variable you set on plain `:root` in your own CSS loses to a
|
|
69
|
+
generated theme that sets the same variable, even when your rule loads later.
|
|
70
|
+
Put the value in the theme, or raise your selector:
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
:root:root {
|
|
74
|
+
--c15t-primary: #2f6f4e;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Scoped rules such as `[data-prompt] { --c15t-primary: ... }` set the variable
|
|
79
|
+
on the banner element itself, so they still apply inside it.
|
|
80
|
+
|
|
81
|
+
## Every token
|
|
82
|
+
|
|
83
|
+
Every framework uses the same tokens. A theme object, such as `ConsentTheme`'s
|
|
84
|
+
`theme`, Astro's and Vue's `theme` or the script tag's `ui.theme`, takes the
|
|
85
|
+
theme key, such as `radius.lg`. Vue and Nuxt `tokens` take the CSS variable
|
|
86
|
+
name without the leading `--`, such as `c15t-radius-lg`. A stylesheet sets the
|
|
87
|
+
CSS variable itself. Your framework's customize page shows where each one goes.
|
|
88
|
+
[Motion and animation](./motion.md) explains the duration and
|
|
89
|
+
easing tokens.
|
|
90
|
+
|
|
91
|
+
| CSS variable | Theme key | Default |
|
|
92
|
+
| ----------------------------- | -------------------------------- | ----------------------------------------------- |
|
|
93
|
+
| `--c15t-primary` | `colors.primary` | `hsl(228, 100%, 60%)` |
|
|
94
|
+
| `--c15t-primary-hover` | `colors.primaryHover` | `hsl(228, 100%, 55%)` |
|
|
95
|
+
| `--c15t-surface` | `colors.surface` | `hsl(0, 0%, 100%)` |
|
|
96
|
+
| `--c15t-surface-hover` | `colors.surfaceHover` | `hsl(0, 0%, 98%)` |
|
|
97
|
+
| `--c15t-border` | `colors.border` | `hsl(0, 0%, 90%)` |
|
|
98
|
+
| `--c15t-border-hover` | `colors.borderHover` | `hsl(0, 0%, 85%)` |
|
|
99
|
+
| `--c15t-text` | `colors.text` | `hsl(0, 0%, 10%)` |
|
|
100
|
+
| `--c15t-text-muted` | `colors.textMuted` | `hsl(0, 0%, 40%)` |
|
|
101
|
+
| `--c15t-text-on-primary` | `colors.textOnPrimary` | auto-derived from `colors.primary` when omitted |
|
|
102
|
+
| `--c15t-overlay` | `colors.overlay` | `hsla(0, 0%, 0%, 0.5)` |
|
|
103
|
+
| `--c15t-switch-track` | `colors.switchTrack` | `hsl(0, 0%, 85%)` |
|
|
104
|
+
| `--c15t-switch-track-active` | `colors.switchTrackActive` | `hsl(228, 100%, 60%)` |
|
|
105
|
+
| `--c15t-switch-thumb` | `colors.switchThumb` | `hsl(0, 0%, 100%)` |
|
|
106
|
+
| `--c15t-font-family` | `typography.fontFamily` | `system-ui, -apple-system, sans-serif` |
|
|
107
|
+
| `--c15t-font-size-sm` | `typography.fontSize.sm` | `0.875rem` |
|
|
108
|
+
| `--c15t-font-size-base` | `typography.fontSize.base` | `1rem` |
|
|
109
|
+
| `--c15t-font-size-lg` | `typography.fontSize.lg` | `1.125rem` |
|
|
110
|
+
| `--c15t-font-weight-normal` | `typography.fontWeight.normal` | `400` |
|
|
111
|
+
| `--c15t-font-weight-medium` | `typography.fontWeight.medium` | `500` |
|
|
112
|
+
| `--c15t-font-weight-semibold` | `typography.fontWeight.semibold` | `600` |
|
|
113
|
+
| `--c15t-line-height-tight` | `typography.lineHeight.tight` | `1.25` |
|
|
114
|
+
| `--c15t-line-height-normal` | `typography.lineHeight.normal` | `1.5` |
|
|
115
|
+
| `--c15t-line-height-relaxed` | `typography.lineHeight.relaxed` | `1.75` |
|
|
116
|
+
| `--c15t-space-xs` | `spacing.xs` | `0.25rem` |
|
|
117
|
+
| `--c15t-space-sm` | `spacing.sm` | `0.5rem` |
|
|
118
|
+
| `--c15t-space-md` | `spacing.md` | `1rem` |
|
|
119
|
+
| `--c15t-space-lg` | `spacing.lg` | `1.5rem` |
|
|
120
|
+
| `--c15t-space-xl` | `spacing.xl` | `2rem` |
|
|
121
|
+
| `--c15t-radius-sm` | `radius.sm` | `0.25rem` |
|
|
122
|
+
| `--c15t-radius-md` | `radius.md` | `0.5rem` |
|
|
123
|
+
| `--c15t-radius-lg` | `radius.lg` | `0.75rem` |
|
|
124
|
+
| `--c15t-radius-full` | `radius.full` | `9999px` |
|
|
125
|
+
| `--c15t-shadow-sm` | `shadows.sm` | `0 1px 2px hsla(0, 0%, 0%, 0.05)` |
|
|
126
|
+
| `--c15t-shadow-md` | `shadows.md` | `0 4px 12px hsla(0, 0%, 0%, 0.08)` |
|
|
127
|
+
| `--c15t-shadow-lg` | `shadows.lg` | `0 8px 24px hsla(0, 0%, 0%, 0.12)` |
|
|
128
|
+
| `--c15t-duration-fast` | `motion.duration.fast` | `80ms` |
|
|
129
|
+
| `--c15t-duration-normal` | `motion.duration.normal` | `150ms` |
|
|
130
|
+
| `--c15t-duration-slow` | `motion.duration.slow` | `200ms` |
|
|
131
|
+
| `--c15t-easing` | `motion.easing` | `cubic-bezier(0.4, 0, 0.2, 1)` |
|
|
132
|
+
| `--c15t-easing-out` | `motion.easingOut` | `cubic-bezier(0.215, 0.61, 0.355, 1)` |
|
|
133
|
+
| `--c15t-easing-in-out` | `motion.easingInOut` | `cubic-bezier(0.645, 0.045, 0.355, 1)` |
|
|
134
|
+
| `--c15t-easing-spring` | `motion.easingSpring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
|
|
135
|
+
|
|
136
|
+
The radius tokens round different parts. `radius.lg` rounds the banner card,
|
|
137
|
+
the preference dialog, `ConsentGate` placeholders and the floating trigger.
|
|
138
|
+
`radius.md` rounds buttons, accordions, tabs and the vendor list.
|
|
139
|
+
`radius.sm` rounds small parts inside the banner and dialog. To give the banner
|
|
140
|
+
and its buttons the same 4px corners, set both `lg` and `md`.
|
|
141
|
+
|
|
118
142
|
## Target a prompt with CSS
|
|
119
143
|
|
|
120
144
|
```css
|
|
@@ -126,8 +150,9 @@ and put the result in a `<style>` element or your stylesheet. See
|
|
|
126
150
|
```
|
|
127
151
|
|
|
128
152
|
Use attributes exposed by the rendered component, not guessed class names.
|
|
129
|
-
|
|
130
|
-
|
|
153
|
+
The script tag's banner has no `data-prompt` or `data-model`. Test the prompt
|
|
154
|
+
and the preferences dialog separately because tokens scoped to one prompt do
|
|
155
|
+
not automatically reach a portaled dialog.
|
|
131
156
|
|
|
132
157
|
| Size variable | Default | Target |
|
|
133
158
|
| ----------------------------------- | ------- | ------------- |
|
|
@@ -135,5 +160,47 @@ one prompt do not automatically reach a portaled dialog.
|
|
|
135
160
|
| `--consent-banner-widget-max-width` | `20rem` | Widget |
|
|
136
161
|
| `--consent-banner-wall-max-width` | `30rem` | Choice wall |
|
|
137
162
|
|
|
163
|
+
The banner footer lays out its actions by the card's width, not the
|
|
164
|
+
viewport's. With the default `compact` profile, a card narrower than 22rem
|
|
165
|
+
puts Reject and Accept on one row and Customize on a full-width row below
|
|
166
|
+
them, on any screen size.
|
|
167
|
+
|
|
138
168
|
Test long translations and small screens after changing width or typography.
|
|
139
169
|
A compact banner must still fit the required actions.
|
|
170
|
+
|
|
171
|
+
## Restyle the "Secured by" tag
|
|
172
|
+
|
|
173
|
+
The tag sits on the edge of the banner and dialog cards and uses the primary
|
|
174
|
+
color by default. Set these variables on `:root`, or on an element that
|
|
175
|
+
contains the tag. The dialog renders in a portal, so a variable set on the
|
|
176
|
+
banner does not reach the dialog's tag.
|
|
177
|
+
|
|
178
|
+
| Variable | Default | Target |
|
|
179
|
+
| -------------------------------------------- | --------------------------------------- | -------------------------------------- |
|
|
180
|
+
| `--consent-branding-tag-background-color` | `var(--c15t-primary)` | Tag background |
|
|
181
|
+
| `--consent-branding-tag-border-color` | `--c15t-primary` mixed 14% toward black | Tag border |
|
|
182
|
+
| `--consent-branding-tag-text-color` | `var(--c15t-text-on-primary, #fff)` | "Secured by" and the wordmark |
|
|
183
|
+
| `--consent-branding-tag-mark-color` | The text color | c15t mark or inth logo |
|
|
184
|
+
| `--consent-branding-tag-shadow` | Inset highlight and a 1px drop shadow | Tag shadow |
|
|
185
|
+
| `--consent-branding-tag-attached-edge-width` | `0px` | Border on the edge that meets the card |
|
|
186
|
+
|
|
187
|
+
The stylesheet does not declare these variables. Each default resolves on the
|
|
188
|
+
tag, so a `--c15t-primary` you scope to a banner still colors the tag.
|
|
189
|
+
|
|
190
|
+
This makes the tag look like a tab of the card:
|
|
191
|
+
|
|
192
|
+
```css
|
|
193
|
+
:root {
|
|
194
|
+
--consent-branding-tag-background-color: var(--c15t-surface);
|
|
195
|
+
--consent-branding-tag-border-color: var(--c15t-border);
|
|
196
|
+
--consent-branding-tag-text-color: var(--c15t-text-muted);
|
|
197
|
+
--consent-branding-tag-mark-color: var(--c15t-primary);
|
|
198
|
+
--consent-branding-tag-shadow: none;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The edge that meets the card has no border by default. Above the banner the
|
|
203
|
+
tag overlaps the card's top border by 1px and covers it. Below the dialog the
|
|
204
|
+
tag starts under the card's bottom border. Set
|
|
205
|
+
`--consent-branding-tag-attached-edge-width: 1px` to draw that edge. It is
|
|
206
|
+
drawn over the card's border, so the two borders do not stack.
|
|
@@ -27,9 +27,25 @@ i18n: {
|
|
|
27
27
|
|
|
28
28
|
Supply the same message keys in each supported locale. A one-off component prop
|
|
29
29
|
such as `dismissButtonText` is useful for one banner; use translations for a
|
|
30
|
-
site-wide change. Astro's serializable integration options
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
site-wide change. Astro's serializable integration options have their own
|
|
31
|
+
types, so verify that shape before copying a React object. The Vue plugin and
|
|
32
|
+
Nuxt module have no `i18n` option; their copy comes from the backend.
|
|
33
|
+
|
|
34
|
+
## Combine messages with backend copy
|
|
35
|
+
|
|
36
|
+
With a backend or a manifest, the copy it sends for the visitor's language is
|
|
37
|
+
the base. Your `i18n.messages` for that language replace it key by key, and
|
|
38
|
+
keys you leave out keep the backend's wording, including copy edited in your
|
|
39
|
+
Inth project. c15t looks up messages for the exact language first, then for
|
|
40
|
+
its primary language, so `de-AT` uses your `de` messages when there is no
|
|
41
|
+
`de-AT` entry. Messages for other languages are not applied.
|
|
42
|
+
|
|
43
|
+
A key only overrides the backend when your text differs from c15t's built-in
|
|
44
|
+
wording for that language. So passing the stock bundles from
|
|
45
|
+
`@c15t/translations/all` to enable languages keeps backend edits visible,
|
|
46
|
+
while a key you actually reworded stays pinned in code. Core bundles only
|
|
47
|
+
English; for other languages, import `@c15t/translations/all` so c15t can
|
|
48
|
+
recognize its stock wording.
|
|
33
49
|
|
|
34
50
|
## Write labels that describe the action
|
|
35
51
|
|
|
@@ -41,6 +57,47 @@ The notice acknowledgement uses `common.acknowledge`, with `common.dismiss` as
|
|
|
41
57
|
a fallback for older translation bundles. Keep the displayed label and the
|
|
42
58
|
command's effect aligned.
|
|
43
59
|
|
|
60
|
+
## Translate the ConsentGate placeholder
|
|
61
|
+
|
|
62
|
+
The `ConsentGate` placeholder reads the `consentGate` section:
|
|
63
|
+
|
|
64
|
+
| Key | Where it shows |
|
|
65
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `consentGate.title` | The placeholder text. `{category}` is replaced by the category's translated title. |
|
|
67
|
+
| `consentGate.actionButton` | The button that opens preferences, with the same `{category}` replacement. |
|
|
68
|
+
| `consentGate.policyBlocked` | React and Vue show it in place of the title, with no button, when a strict policy leaves the category out of scope. |
|
|
69
|
+
|
|
70
|
+
Earlier versions called this section `frame`. Copy under `frame` in
|
|
71
|
+
`i18n.messages` or custom translations still applies. c15t reads it as `consentGate`, a key set under `consentGate`
|
|
72
|
+
wins over the same key under `frame`, and c15t logs a warning once outside
|
|
73
|
+
production. Rename the section to `consentGate` to remove the warning.
|
|
74
|
+
|
|
75
|
+
## Show right-to-left languages
|
|
76
|
+
|
|
77
|
+
c15t sets `dir="rtl"` on its surfaces when the resolved language is Arabic,
|
|
78
|
+
Hebrew, Persian, Urdu, Pashto, Sindhi, Kurdish or Dhivehi. It matches the
|
|
79
|
+
primary language, so `ar-EG` counts as Arabic. Hebrew is the only
|
|
80
|
+
right-to-left translation c15t ships. For the others, supply your own
|
|
81
|
+
messages or the copy from your Inth project.
|
|
82
|
+
|
|
83
|
+
What follows the direction:
|
|
84
|
+
|
|
85
|
+
* Text, headings and the button row in the banner, the preference dialog and
|
|
86
|
+
the preference widget flow right to left.
|
|
87
|
+
* A floating or widget banner in its default corner moves to the mirrored
|
|
88
|
+
corner, so `bottom-left` becomes `bottom-right`. A `position` you set
|
|
89
|
+
yourself stays where you put it.
|
|
90
|
+
* The HTML script tag, `@c15t/browser` and Astro's banner also set `lang` on
|
|
91
|
+
their surfaces. React, Vue and Svelte set `dir` only, so set `lang` on
|
|
92
|
+
`<html>` yourself.
|
|
93
|
+
|
|
94
|
+
What does not follow yet:
|
|
95
|
+
|
|
96
|
+
* The IAB TCF dialog aligns several labels, indents and borders to the left.
|
|
97
|
+
* The floating `ConsentDialogTrigger` keeps the corner you give it.
|
|
98
|
+
|
|
99
|
+
Test right-to-left pages with a real translation before you ship them.
|
|
100
|
+
|
|
44
101
|
## Test more than English
|
|
45
102
|
|
|
46
103
|
Try the longest labels you support at a narrow width, with browser zoom and
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Callbacks
|
|
3
|
+
description: Run code in a React app when a visitor records a consent choice,
|
|
4
|
+
when permissions change, when a request fails and before the revocation
|
|
5
|
+
reload, with the ConsentProvider callbacks option.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pass callbacks to the provider
|
|
10
|
+
|
|
11
|
+
Keep the callbacks in their own module:
|
|
12
|
+
|
|
13
|
+
```ts title="src/consent-callbacks.ts"
|
|
14
|
+
import type { ConsentProviderCallbacks } from 'c15t/react';
|
|
15
|
+
|
|
16
|
+
export const callbacks = {
|
|
17
|
+
// Right before the page reloads because a save withdrew a permission.
|
|
18
|
+
onBeforeConsentRevocationReload: ({ preferences }) => {
|
|
19
|
+
console.info('Reloading with', preferences);
|
|
20
|
+
},
|
|
21
|
+
// The visitor clicked Accept all, Reject all or Save.
|
|
22
|
+
onChoiceRecorded: ({ confirmed, snapshot }) => {
|
|
23
|
+
console.info('Choice recorded', confirmed, snapshot.explicitChoice);
|
|
24
|
+
},
|
|
25
|
+
// A consent command failed, such as a save the backend rejected.
|
|
26
|
+
onError: ({ error }) => {
|
|
27
|
+
console.error('c15t', error);
|
|
28
|
+
},
|
|
29
|
+
// Permissions changed for any reason: a choice, an expired choice, a new
|
|
30
|
+
// policy or a privacy signal.
|
|
31
|
+
onPermissionsChanged: ({ previous, snapshot }) => {
|
|
32
|
+
console.info('Permissions', previous, snapshot.effectivePermissions);
|
|
33
|
+
},
|
|
34
|
+
} satisfies ConsentProviderCallbacks;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Pass them in the `ConsentProvider` options, in the file that renders the
|
|
38
|
+
provider, such as `src/consent.tsx` from the
|
|
39
|
+
[quickstart](./quickstart.md):
|
|
40
|
+
|
|
41
|
+
```tsx title="src/consent.tsx"
|
|
42
|
+
<ConsentProvider options={{ mode, scripts, callbacks }}>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The provider subscribes the callbacks after it mounts, so they run in the
|
|
46
|
+
browser. If you render on the server, server rendering never calls them. The
|
|
47
|
+
provider calls the latest `callbacks` object it received, so a new object on
|
|
48
|
+
a later render takes effect without a remount. Defining the object at module
|
|
49
|
+
level, as above, keeps it the same across renders.
|
|
50
|
+
|
|
51
|
+
When you pass your own `runtime` to `ConsentProvider`, the provider does not
|
|
52
|
+
wire `callbacks` or `reloadOnConsentRevoked` from its options. Pass them to
|
|
53
|
+
`createConsentRuntime` instead.
|
|
54
|
+
|
|
55
|
+
## Available callbacks
|
|
56
|
+
|
|
57
|
+
| Callback | Runs when | Receives |
|
|
58
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `onChoiceRecorded` | The visitor clicks Accept All, Reject All or Save Settings, in the stock UI or through `useSaveConsents()`. | `snapshot`, the state after the choice; `confirmed`, the categories this action recorded; `actionAt`, the time as a millisecond timestamp |
|
|
60
|
+
| `onPermissionsChanged` | Any effective permission changes value. | `snapshot`, the state after the change; `previous`, the permissions before it |
|
|
61
|
+
| `onError` | A consent command fails, such as a failed `/init` request or a save the backend rejects. | `error`, a message string |
|
|
62
|
+
| `onBeforeConsentRevocationReload` | A save withdrew a granted category or vendor, right before c15t reloads the page. | `preferences`, the permissions after the save |
|
|
63
|
+
|
|
64
|
+
The type for the whole object is `ConsentProviderCallbacks` from `c15t/react`.
|
|
65
|
+
Callbacks run after c15t updates its state, so `snapshot` and hooks such as
|
|
66
|
+
`useConsent()` already hold the new values.
|
|
67
|
+
|
|
68
|
+
## onChoiceRecorded or onPermissionsChanged
|
|
69
|
+
|
|
70
|
+
`onChoiceRecorded` runs only for a visitor's action. `onPermissionsChanged`
|
|
71
|
+
runs whenever effective permissions change, whatever the cause:
|
|
72
|
+
|
|
73
|
+
| Event | `onChoiceRecorded` | `onPermissionsChanged` |
|
|
74
|
+
| --------------------------------------------------------- | ------------------ | ---------------------------- |
|
|
75
|
+
| Visitor accepts, rejects or saves | Yes | Yes, if a permission changed |
|
|
76
|
+
| A policy resolves and allows categories before any choice | No | Yes |
|
|
77
|
+
| Global Privacy Control turns off a grant | No | Yes |
|
|
78
|
+
| A recorded choice expires | No | Yes |
|
|
79
|
+
| Visitor dismisses a notice | No | No |
|
|
80
|
+
|
|
81
|
+
Use `onChoiceRecorded` for consent analytics and audit trails, because it
|
|
82
|
+
fires only when the visitor chose. Use `onPermissionsChanged` to start or stop
|
|
83
|
+
your own code. Never treat it as proof that the visitor agreed to anything,
|
|
84
|
+
and never save consent from it.
|
|
85
|
+
[How consent works](../../concepts/how-consent-works.md#a-permission-is-not-a-recorded-choice)
|
|
86
|
+
explains the difference.
|
|
87
|
+
|
|
88
|
+
Dismissing a notice records no choice and changes no permission. Read it with
|
|
89
|
+
`useNoticeDismissal()` instead. For consent inside one component, use the
|
|
90
|
+
[hooks](./hooks.md), which re-render the component when
|
|
91
|
+
consent changes.
|
|
92
|
+
|
|
93
|
+
## Before a revocation reload
|
|
94
|
+
|
|
95
|
+
Removing a script tag cannot stop code that already ran, so c15t reloads the
|
|
96
|
+
page after an accept, reject or save turns off a category or vendor that was
|
|
97
|
+
granted. The reload waits for the save request to finish. Right before it,
|
|
98
|
+
`onBeforeConsentRevocationReload` runs synchronously. Use it to call a
|
|
99
|
+
vendor's shutdown API or flush queued events. Keep it short, because the
|
|
100
|
+
reload does not wait for promises.
|
|
101
|
+
|
|
102
|
+
A choice that expires, a new policy and a privacy signal change permissions
|
|
103
|
+
without a visitor action, so they do not reload the page.
|
|
104
|
+
|
|
105
|
+
To handle revocation yourself, set `reloadOnConsentRevoked: false` in the
|
|
106
|
+
provider options. Code the visitor turned off then keeps running until the
|
|
107
|
+
next full page load, and `onBeforeConsentRevocationReload` never runs.
|
|
108
|
+
|
|
109
|
+
## Verify
|
|
110
|
+
|
|
111
|
+
1. Add the callbacks from this page, open the console and DevTools Network,
|
|
112
|
+
clear site data and reload under a policy that shows a banner.
|
|
113
|
+
2. Click **Accept All**. `onChoiceRecorded` and `onPermissionsChanged` both log,
|
|
114
|
+
and the save request to your backend appears in Network.
|
|
115
|
+
3. Reload. `onChoiceRecorded` does not log again.
|
|
116
|
+
4. Open Privacy settings, turn off a category you allowed and save.
|
|
117
|
+
`onBeforeConsentRevocationReload` logs, then the page reloads. After the
|
|
118
|
+
reload, that category's vendor requests are absent from Network.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Clear on revocation
|
|
3
|
+
description: Delete the first-party cookies and Web Storage keys a consent
|
|
4
|
+
category owns in a React app when that category is denied, with the
|
|
5
|
+
clearOnRevocation option on ConsentProvider.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Configure cleanup
|
|
10
|
+
|
|
11
|
+
Gating stops new requests, but it leaves the cookies and storage keys a vendor
|
|
12
|
+
already wrote. List the ones each optional category owns and add them to the
|
|
13
|
+
provider options in `src/consent.tsx`. When that category is denied, c15t
|
|
14
|
+
deletes them:
|
|
15
|
+
|
|
16
|
+
```tsx title="src/consent.tsx (partial)"
|
|
17
|
+
import type { ClearOnRevocationConfig } from 'c15t';
|
|
18
|
+
|
|
19
|
+
const clearOnRevocation: ClearOnRevocationConfig = {
|
|
20
|
+
measurement: { cookies: ['_ga', '_ga_*'], localStorage: ['analytics:*'] },
|
|
21
|
+
marketing: { cookies: ['_fbp'], sessionStorage: ['campaign-id'] },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
<ConsentProvider options={{ mode, scripts, clearOnRevocation }}>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The provider reads `clearOnRevocation` once, when it mounts; remount it to
|
|
28
|
+
change the list. A provider that borrows a `runtime` does not read it.
|
|
29
|
+
Configure cleanup on the runtime owner instead, with
|
|
30
|
+
`createConsentRuntime({ clearOnRevocation })`.
|
|
31
|
+
|
|
32
|
+
## Name cookies and keys
|
|
33
|
+
|
|
34
|
+
Each optional category takes three lists. Without `clearOnRevocation`, c15t
|
|
35
|
+
deletes nothing, and `necessary` cannot be configured.
|
|
36
|
+
|
|
37
|
+
| Field | Takes |
|
|
38
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
39
|
+
| `cookies` | Cookie names, prefixes ending in `*` such as `'_ga_*'`, or `{ name, domain?, path?, partitioned? }` objects. |
|
|
40
|
+
| `localStorage` | Keys, or prefixes ending in `*`. |
|
|
41
|
+
| `sessionStorage` | Keys, or prefixes ending in `*`. |
|
|
42
|
+
|
|
43
|
+
Use an exact name, or a prefix followed by `*`. `_ga_*` matches `_ga_ABC123`
|
|
44
|
+
but not `_ga`, so list both. Regular expressions, a `*` anywhere but the end,
|
|
45
|
+
and a bare `*` are not supported. Cookie names are matched as written,
|
|
46
|
+
without URL decoding.
|
|
47
|
+
|
|
48
|
+
By default c15t tries the current host and its parent domains, and the
|
|
49
|
+
current path and its parents. For a cookie set on a specific scope, use an
|
|
50
|
+
object:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const cookies = [
|
|
54
|
+
{ name: 'analytics-id', domain: 'example.com', path: '/' },
|
|
55
|
+
{ name: 'checkout-metrics', domain: '', path: '/checkout' },
|
|
56
|
+
{ name: 'partitioned-metrics', partitioned: true },
|
|
57
|
+
];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
An empty `domain` means a host-only cookie. A `domain` or `path` you set
|
|
61
|
+
replaces the automatic attempts for that field. Partitioned cookies need
|
|
62
|
+
`partitioned: true`. A prefix only finds cookies the current page can read,
|
|
63
|
+
so use an exact name for a cookie on another path.
|
|
64
|
+
|
|
65
|
+
c15t never deletes its own consent records, even when a pattern matches
|
|
66
|
+
them.
|
|
67
|
+
|
|
68
|
+
## When cleanup runs
|
|
69
|
+
|
|
70
|
+
* On the first page load after the policy resolves, for every denied
|
|
71
|
+
category. This covers a new opt-in visitor and a returning visitor whose
|
|
72
|
+
choice expired.
|
|
73
|
+
* Whenever a category changes from allowed to denied, after a saved refusal,
|
|
74
|
+
expiry, a policy change or a Global Privacy Control signal.
|
|
75
|
+
|
|
76
|
+
Cleanup does not run during server rendering, while the policy is still
|
|
77
|
+
loading, on draft switch changes in the dialog, or when the provider
|
|
78
|
+
unmounts. It runs once per change and does not poll. Under an opt-out
|
|
79
|
+
policy, an expired choice that stays allowed deletes nothing. If a resolved
|
|
80
|
+
policy later falls back to denial after a failed request, cleanup deletes
|
|
81
|
+
the data, and a successful retry cannot restore it.
|
|
82
|
+
|
|
83
|
+
Switching off one vendor inside an allowed category deletes nothing, because
|
|
84
|
+
the category stays allowed.
|
|
85
|
+
|
|
86
|
+
## Browser limits
|
|
87
|
+
|
|
88
|
+
c15t can delete first-party cookies that JavaScript can read, and keys in
|
|
89
|
+
the current origin's `localStorage` and `sessionStorage`. It cannot delete
|
|
90
|
+
`HttpOnly` cookies or another origin's data; expire those from your server.
|
|
91
|
+
A vendor SDK still running on the page can write its cookies again, which is
|
|
92
|
+
one reason c15t reloads the page after a revocation. Deletion failures do
|
|
93
|
+
not block the consent update. See
|
|
94
|
+
[MDN on cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies).
|
|
95
|
+
|
|
96
|
+
## Verify cleanup
|
|
97
|
+
|
|
98
|
+
1. Allow measurement and let Google Analytics run. In DevTools Application,
|
|
99
|
+
Cookies shows `_ga` and a `_ga_<id>` cookie.
|
|
100
|
+
2. Open Privacy settings, turn measurement off and save. c15t reloads the
|
|
101
|
+
page. Both cookies are gone, and `analytics:*` keys are gone from Local
|
|
102
|
+
storage.
|
|
103
|
+
3. Reload. The cookies do not come back, because Google Analytics no longer
|
|
104
|
+
loads.
|
|
105
|
+
4. If you listed a cookie with an explicit `domain` or `path`, repeat step 2
|
|
106
|
+
and check that the cookie is gone from that scope too.
|