@c15t/react 3.0.0-alpha.2 → 3.0.0-alpha.4
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 +91 -67
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/aggregate-components.js +1 -1
- package/dist/chunk-warming.js +1 -1
- 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/component-hooks/use-panel-trigger.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/consent-preferences-link/consent-preferences-link.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/atoms/purpose-item.js +1 -1
- package/dist/components/iab-panel/atoms/stack-item.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/overlay.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel/panel.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/icon.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
- package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.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/prompt/components.js +1 -1
- package/dist/components/prompt/policy-actions.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/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/components/shared/ui/logo.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/consent-theme.js +1 -0
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/hooks/use-committed-ref.js +2 -0
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +5 -45
- package/dist/iab-context.js +1 -1
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -0
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/module-hooks/network-blocker.js +1 -1
- package/dist/module-hooks/network-hold.js +2 -0
- package/dist/module-hooks/persistence.js +1 -1
- package/dist/panel.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/preferences.js +1 -2
- 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 -61
- package/dist/transports/offline.js +1 -1
- package/dist/ui-save.js +1 -1
- package/dist/utils/deferred-module.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/chunk-warming.d.ts +41 -8
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
- package/dist-types/component-hooks/use-headless-iab-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/atoms/purpose-item.d.ts +4 -1
- package/dist-types/components/iab-panel/atoms/stack-item.d.ts +6 -1
- package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +6 -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/panel/index.d.ts +1 -0
- package/dist-types/components/panel/panel.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +20 -0
- package/dist-types/components/preferences/index.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/accordion/accordion.d.ts +1 -0
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +1 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/components/shared/ui/logo.d.ts +0 -10
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +14 -0
- package/dist-types/components/shared/ui/switch/switch.d.ts +1 -0
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -0
- package/dist-types/consent-theme.d.ts +38 -0
- 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.d.ts +50 -7
- package/dist-types/headless.d.ts +0 -1
- package/dist-types/hooks/use-committed-ref.d.ts +24 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +56 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index.d.ts +4 -3
- package/dist-types/kernel-selector.d.ts +66 -0
- package/dist-types/module-hooks/iframe-blocker.d.ts +27 -0
- package/dist-types/module-hooks/network-blocker.d.ts +23 -0
- package/dist-types/module-hooks/network-hold.d.ts +47 -0
- package/dist-types/panel.d.ts +13 -1
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/preferences.d.ts +12 -1
- package/dist-types/provider.d.ts +62 -12
- 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 +9 -1
- package/dist-types/ui-save.d.ts +21 -1
- package/dist-types/utils/deferred-module.d.ts +3 -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 +91 -67
- 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 +30 -8
- package/docs/concepts/consent-state.md +357 -0
- 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 -33
- package/docs/customization/recipes.md +839 -47
- 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 +166 -36
- package/docs/customization/translations.md +61 -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 +100 -55
- package/docs/frameworks/react/components/consent-gate.md +45 -24
- package/docs/frameworks/react/components/consent-provider.md +126 -0
- package/docs/frameworks/react/components/consent-widget.md +32 -20
- package/docs/frameworks/react/components/dev-tools.md +52 -84
- 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/hooks.md +65 -0
- package/docs/frameworks/react/iab.md +222 -0
- package/docs/frameworks/react/network-blocker.md +140 -0
- 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 +168 -160
- package/docs/integrations/ahrefs-analytics.md +153 -155
- package/docs/integrations/amplitude.md +163 -157
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +155 -155
- package/docs/integrations/cloudflare-web-analytics.md +157 -157
- package/docs/integrations/cloudflare-zaraz.md +210 -262
- package/docs/integrations/crisp.md +165 -159
- package/docs/integrations/databuddy.md +158 -174
- package/docs/integrations/fathom-analytics.md +159 -157
- package/docs/integrations/front-chat.md +322 -0
- package/docs/integrations/google-maps.md +119 -84
- package/docs/integrations/google-tag-manager.md +179 -164
- package/docs/integrations/google-tag.md +164 -161
- package/docs/integrations/heap.md +164 -156
- package/docs/integrations/hightouch.md +162 -158
- package/docs/integrations/hotjar.md +160 -156
- package/docs/integrations/intercom.md +184 -154
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +175 -151
- package/docs/integrations/logrocket.md +161 -157
- package/docs/integrations/matomo-analytics.md +189 -179
- package/docs/integrations/meta-pixel.md +189 -151
- package/docs/integrations/microsoft-clarity.md +164 -156
- package/docs/integrations/microsoft-uet.md +149 -155
- package/docs/integrations/mixpanel-analytics.md +156 -161
- package/docs/integrations/one-dollar-stats.md +306 -0
- package/docs/integrations/openai-pixel.md +205 -302
- package/docs/integrations/overview.md +143 -83
- package/docs/integrations/pinterest-tag.md +329 -0
- package/docs/integrations/pirsch.md +170 -160
- package/docs/integrations/plausible-analytics.md +173 -159
- package/docs/integrations/posthog.md +227 -242
- package/docs/integrations/promptwatch.md +155 -155
- package/docs/integrations/reddit-pixel.md +186 -158
- package/docs/integrations/rudderstack.md +202 -187
- package/docs/integrations/rybbit-analytics.md +172 -161
- package/docs/integrations/segment.md +183 -155
- package/docs/integrations/snapchat-pixel.md +187 -157
- package/docs/integrations/tiktok-pixel.md +172 -151
- package/docs/integrations/umami-analytics.md +164 -159
- package/docs/integrations/vercel-analytics.md +167 -158
- package/docs/integrations/x-pixel.md +177 -151
- package/docs/integrations/youtube.md +122 -87
- package/docs/upgrade-v3.md +490 -354
- package/package.json +15 -9
- package/dist/component-hooks/use-manager.js +0 -2
- package/dist-types/component-hooks/use-manager.d.ts +0 -45
- 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 -75
- package/docs/frameworks/react/hooks/use-consent-manager/overview.md +0 -58
- package/docs/frameworks/react/iab/overview.md +0 -53
- package/docs/frameworks/react/script-loader.md +0 -63
- package/docs/frameworks/react/styling/overview.md +0 -217
- package/docs/guides/consent-state.md +0 -60
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -208
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Customize
|
|
3
|
+
description: Change the colors, fonts, layout, button styles and copy of the
|
|
4
|
+
c15t banner and dialog in a React app with the stylesheet, theme tokens,
|
|
5
|
+
provider options and slots.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pick the right tool
|
|
10
|
+
|
|
11
|
+
| Change | Use |
|
|
12
|
+
| ------------------------------------ | ------------------------------------------------------------------- |
|
|
13
|
+
| Brand colors, fonts, radius, spacing | [Theme tokens](#change-colors-fonts-and-radius) |
|
|
14
|
+
| Light and dark colors, animation | [Dark mode and motion](#turn-on-dark-mode-and-change-motion) |
|
|
15
|
+
| Which button is filled or outlined | [`consentActions`](#style-the-action-buttons) |
|
|
16
|
+
| Banner shape and position | [`ConsentBanner` props or `presentation`](#change-the-banner-shape) |
|
|
17
|
+
| One element of a component | [Component parts](#style-one-component-part) |
|
|
18
|
+
| Labels and languages | [Copy and translations](#change-the-copy) |
|
|
19
|
+
| Different markup entirely | [Headless](./headless.md) |
|
|
20
|
+
|
|
21
|
+
[Customization](../../customization/overview.md) explains how these choices relate
|
|
22
|
+
across frameworks.
|
|
23
|
+
|
|
24
|
+
See the [design gallery](../../customization/recipes.md) for five banner designs,
|
|
25
|
+
from a bottom bar to a fully custom one, with tested code for this framework.
|
|
26
|
+
|
|
27
|
+
## Import the stylesheet
|
|
28
|
+
|
|
29
|
+
Import the stylesheet once, in the module that renders `ConsentProvider`. The
|
|
30
|
+
[quickstart](./quickstart.md) does this in `src/consent.tsx`:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import 'c15t/react/styles.css';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The import is the same with or without Tailwind. To import the stylesheet from
|
|
37
|
+
CSS instead, put it at the top of your global stylesheet, above any
|
|
38
|
+
`@tailwind` directives. `postcss-import`, which Vite uses, ignores an
|
|
39
|
+
`@import` that follows other rules. Tailwind 3 also needs a PostCSS plugin;
|
|
40
|
+
[Tailwind CSS](../../customization/tailwind.md#set-up-tailwind-css-3) shows the
|
|
41
|
+
setup.
|
|
42
|
+
|
|
43
|
+
## Change colors, fonts and radius
|
|
44
|
+
|
|
45
|
+
Colors, radii, shadows, typography, and motion come from `--c15t-*` custom
|
|
46
|
+
properties. The package stylesheet sets the default values. To change them,
|
|
47
|
+
render `ConsentTheme` with your theme where your app renders on the server,
|
|
48
|
+
or override the variables directly in your CSS.
|
|
49
|
+
|
|
50
|
+
`ConsentTheme` renders a `<style id="c15t-theme">` element with the
|
|
51
|
+
variables for `theme`. It is not a client component: rendered from a Server
|
|
52
|
+
Component or another server-only file, the theme generator stays out of the
|
|
53
|
+
browser bundle. Rendered from a client component it still works, but the
|
|
54
|
+
browser downloads the generator (about 1.7 KB gzip). The provider no longer
|
|
55
|
+
turns `theme` tokens into CSS. It still reads `consentActions` and slot
|
|
56
|
+
styles from its `theme` option, and warns in development when `theme` holds
|
|
57
|
+
tokens but the page has no `c15t-theme` stylesheet.
|
|
58
|
+
|
|
59
|
+
| `ConsentTheme` prop | Purpose |
|
|
60
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `theme` | Colors, `dark` colors, typography, spacing, radius, shadows and motion. Defaults to the built-in theme. |
|
|
62
|
+
| `colorScheme` | `'dark'` makes the dark tokens the default before hydration. `'system'` follows `prefers-color-scheme` with a CSS media query. Omit it to follow a `dark` or `c15t-dark` class on `<html>`. |
|
|
63
|
+
| `nonce` | Content Security Policy nonce for the `<style>` element. |
|
|
64
|
+
|
|
65
|
+
Pass the same `colorScheme` to `ConsentTheme` and to the provider. If your
|
|
66
|
+
app manages dark mode through a root class, such as next-themes' `dark`
|
|
67
|
+
class, omit `colorScheme` and include that class in the server HTML.
|
|
68
|
+
[Dark mode](../../customization/dark-mode.md) covers `theme.dark`, `null` and
|
|
69
|
+
a dark first paint.
|
|
70
|
+
|
|
71
|
+
To switch between different token sets at runtime, render `ConsentTheme`
|
|
72
|
+
from a client component and change its props, which ships the generator.
|
|
73
|
+
|
|
74
|
+
Outside a component, call `generateThemeCSS(theme, colorScheme)` from
|
|
75
|
+
`c15t/react/utils`, or `@c15t/react/utils` with the scoped package. It
|
|
76
|
+
writes the same CSS as `ConsentTheme`. Outside React, import it from
|
|
77
|
+
`@c15t/ui/theme`. Call it on the server or at build time and put the result in a
|
|
78
|
+
`<style>` element or your stylesheet. It escapes `<`, so the result is safe
|
|
79
|
+
inside `<style>`.
|
|
80
|
+
|
|
81
|
+
A single-page app has no server render, so pick one of two ways:
|
|
82
|
+
|
|
83
|
+
* **Set the variables in your CSS.** Nothing extra ships to the browser. Load
|
|
84
|
+
this after `c15t/react/styles.css`:
|
|
85
|
+
|
|
86
|
+
```css title="src/index.css"
|
|
87
|
+
:root {
|
|
88
|
+
--c15t-primary: #2f6f4e;
|
|
89
|
+
--c15t-primary-hover: #24563c;
|
|
90
|
+
--c15t-text-on-primary: #fff;
|
|
91
|
+
--c15t-radius-lg: 4px;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
* **Render `ConsentTheme` next to the provider.** Use this when the theme lives
|
|
96
|
+
in TypeScript or changes at runtime. The browser downloads the theme
|
|
97
|
+
generator.
|
|
98
|
+
|
|
99
|
+
```tsx title="src/consent-theme.ts"
|
|
100
|
+
import { defineTheme } from 'c15t/react';
|
|
101
|
+
|
|
102
|
+
export const theme = defineTheme({
|
|
103
|
+
colors: { primary: '#2f6f4e', primaryHover: '#24563c' },
|
|
104
|
+
radius: { lg: '4px' },
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
In `src/consent.tsx`, render `<ConsentTheme theme={theme} />` from
|
|
109
|
+
`c15t/react` just before `<ConsentProvider>`.
|
|
110
|
+
|
|
111
|
+
In a server-rendered app, such as React Router framework mode, render
|
|
112
|
+
`ConsentTheme` in the root component so the `<style>` element is part of the
|
|
113
|
+
server HTML. [Theme tokens](../../customization/tokens.md) lists every variable.
|
|
114
|
+
|
|
115
|
+
`ConsentTheme` outranks a plain `:root` rule, wherever each one loads. If you
|
|
116
|
+
use both, a variable set in both places takes the `ConsentTheme` value. Write
|
|
117
|
+
your CSS overrides on `:root:root` to beat it, or move the values into the
|
|
118
|
+
theme.
|
|
119
|
+
|
|
120
|
+
## Style the action buttons
|
|
121
|
+
|
|
122
|
+
`consentActions` decides how each action looks. `default` applies to every
|
|
123
|
+
button, `primary` to the action the policy or your props mark primary, and
|
|
124
|
+
`accept`, `reject`, `customize` and `dismiss` to one action each. Pass it
|
|
125
|
+
through the provider's `theme` option:
|
|
126
|
+
|
|
127
|
+
```tsx title="src/consent.tsx"
|
|
128
|
+
<ConsentProvider
|
|
129
|
+
options={{
|
|
130
|
+
mode,
|
|
131
|
+
scripts,
|
|
132
|
+
theme: {
|
|
133
|
+
consentActions: {
|
|
134
|
+
primary: { variant: 'primary', mode: 'filled' },
|
|
135
|
+
dismiss: { variant: 'neutral', mode: 'stroke' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The provider's `theme` option only reads `consentActions` and slot styles.
|
|
143
|
+
Tokens passed there do nothing, and the provider logs a warning in development.
|
|
144
|
+
Changing button styles does not change the policy or expire a saved choice.
|
|
145
|
+
|
|
146
|
+
## Change the banner shape
|
|
147
|
+
|
|
148
|
+
`ConsentBanner` takes `variant` (`floating`, `bar`, `widget` or `wall`) and
|
|
149
|
+
`position`. To use one shape everywhere, set `presentation.prompt` in the
|
|
150
|
+
provider options instead. This banner is a full-width bar for notices and a
|
|
151
|
+
card for choices:
|
|
152
|
+
|
|
153
|
+
```tsx title="src/regional-banner.tsx"
|
|
154
|
+
import { ConsentBanner, usePolicyRule } from 'c15t/react';
|
|
155
|
+
|
|
156
|
+
export function RegionalBanner() {
|
|
157
|
+
const policy = usePolicyRule();
|
|
158
|
+
return (
|
|
159
|
+
<ConsentBanner variant={policy?.prompt === 'notice' ? 'bar' : 'floating'} />
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Render `RegionalBanner` in place of `ConsentBanner` inside the provider.
|
|
165
|
+
[ConsentBanner](./components/consent-banner.md#variants)
|
|
166
|
+
lists every variant and position, and how to order buttons per policy.
|
|
167
|
+
|
|
168
|
+
## Style one component part
|
|
169
|
+
|
|
170
|
+
Each component part is a slot. Set slots in the provider options under
|
|
171
|
+
`components`, keyed by component and part. A slot takes any attribute the
|
|
172
|
+
element accepts, such as `className` or `style`.
|
|
173
|
+
|
|
174
|
+
The root also carries `data-variant`, so you can restyle one shape without
|
|
175
|
+
touching the others. Tailwind, giving a `bar` a brand-colored top edge and
|
|
176
|
+
tighter text:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<ConsentProvider
|
|
180
|
+
options={{
|
|
181
|
+
mode,
|
|
182
|
+
presentation: { prompt: { variant: 'bar' } },
|
|
183
|
+
components: {
|
|
184
|
+
banner: {
|
|
185
|
+
root: { className: 'group' },
|
|
186
|
+
card: {
|
|
187
|
+
className:
|
|
188
|
+
'group-data-[variant=bar]:border-t-4 group-data-[variant=bar]:border-t-emerald-600',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
description: {
|
|
192
|
+
banner: { className: 'group-data-[variant=bar]:text-xs' },
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`data-variant` sits on the root, so child slots use Tailwind's `group`
|
|
200
|
+
prefix to read it.
|
|
201
|
+
|
|
202
|
+
[Class names and CSS-in-JS](../../customization/class-names.md) shows CSS
|
|
203
|
+
Modules, vanilla-extract, StyleX and Emotion classes on a part.
|
|
204
|
+
|
|
205
|
+
Set `noStyle` on a component to drop the built-in classes from every part and
|
|
206
|
+
keep only what your slots pass. Set `noStyle` in the provider options to do it
|
|
207
|
+
for every component.
|
|
208
|
+
|
|
209
|
+
[Component parts](../../customization/slots.md) lists every part key under
|
|
210
|
+
`components` and the `data-*` attributes each part carries. Action buttons
|
|
211
|
+
carry `data-action`, so a single rule such as `[data-action='dismiss']`
|
|
212
|
+
styles one role across every surface. Right links are underlined text by
|
|
213
|
+
default, so the only button on a notice is the primary action.
|
|
214
|
+
|
|
215
|
+
[Component parts](../../customization/slots.md) lists every component's part keys
|
|
216
|
+
and when to drop the built-in styles with `noStyle`. The `ConsentGate`
|
|
217
|
+
placeholder's parts are `components['consent-gate'].root`, `.title` and
|
|
218
|
+
`.button`.
|
|
219
|
+
[Class names and CSS-in-JS](../../customization/class-names.md) covers CSS
|
|
220
|
+
Modules, vanilla-extract, StyleX and Emotion, and
|
|
221
|
+
[Tailwind CSS](../../customization/tailwind.md) covers utilities on parts.
|
|
222
|
+
|
|
223
|
+
## Turn on dark mode and change motion
|
|
224
|
+
|
|
225
|
+
Pass `colorScheme` in the provider options and the same value to
|
|
226
|
+
`ConsentTheme`. Leave it unset to follow a `dark` class on `<html>`, and add
|
|
227
|
+
dark colors with `theme.dark`. [Dark mode](../../customization/dark-mode.md)
|
|
228
|
+
covers each value and a dark first paint.
|
|
229
|
+
|
|
230
|
+
`disableAnimation` in the provider options turns off the banner and dialog
|
|
231
|
+
animations, and the same prop on `ConsentBanner` or `ConsentDialog` overrides
|
|
232
|
+
it for one surface. [Motion and animation](../../customization/motion.md) covers
|
|
233
|
+
the duration and easing tokens and reduced motion.
|
|
234
|
+
|
|
235
|
+
## Change the copy
|
|
236
|
+
|
|
237
|
+
Labels, descriptions and languages come from the translations in your Inth
|
|
238
|
+
project and from the provider's `i18n` option. For the visitor's language,
|
|
239
|
+
`i18n.messages` replace the project's copy key by key, and keys you leave out
|
|
240
|
+
keep the project's wording.
|
|
241
|
+
[Copy and translations](../../customization/translations.md) shows the message
|
|
242
|
+
keys. For one banner, `ConsentBanner` also accepts text props such as
|
|
243
|
+
`dismissButtonText`.
|
|
244
|
+
|
|
245
|
+
## Check the result
|
|
246
|
+
|
|
247
|
+
Clear site data and reload under a policy that shows a banner. Check the banner
|
|
248
|
+
and the preferences dialog, since both read the same tokens. Test a narrow
|
|
249
|
+
window, keyboard focus and contrast on the primary button. In development, the
|
|
250
|
+
console warns if `theme` holds tokens but the page has no `c15t-theme` style.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Embeds
|
|
3
|
+
description: Keep YouTube videos, maps and other iframes out of a React page
|
|
4
|
+
until their consent category is allowed, with ConsentGate or the iframe
|
|
5
|
+
blocker in ConsentProvider.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pick a method
|
|
10
|
+
|
|
11
|
+
| Method | Use it when |
|
|
12
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `ConsentGate` | You render the iframe in a React component and want a placeholder until the visitor allows its category. |
|
|
14
|
+
| The iframe blocker | The iframe comes from markup you do not render with React, such as CMS or Markdown content. |
|
|
15
|
+
|
|
16
|
+
Both keep the iframe's `src` out of the page until the category is allowed, so
|
|
17
|
+
the embed's host receives no request before consent.
|
|
18
|
+
|
|
19
|
+
## Gate an embed with ConsentGate
|
|
20
|
+
|
|
21
|
+
Wrap the iframe in `ConsentGate` anywhere inside `ConsentProvider`:
|
|
22
|
+
|
|
23
|
+
```tsx title="src/video-embed.tsx"
|
|
24
|
+
import { ConsentDialogLink, ConsentGate } from 'c15t/react';
|
|
25
|
+
|
|
26
|
+
export const VideoEmbed = () => (
|
|
27
|
+
<ConsentGate
|
|
28
|
+
category="measurement"
|
|
29
|
+
placeholder={
|
|
30
|
+
<div className="placeholder">
|
|
31
|
+
<p>Allow measurement to load this YouTube video.</p>
|
|
32
|
+
<ConsentDialogLink>Choose video permissions</ConsentDialogLink>
|
|
33
|
+
</div>
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<iframe
|
|
37
|
+
title="YouTube video"
|
|
38
|
+
sandbox="allow-scripts allow-same-origin allow-presentation"
|
|
39
|
+
src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
|
|
40
|
+
allow="encrypted-media; picture-in-picture"
|
|
41
|
+
allowFullScreen
|
|
42
|
+
/>
|
|
43
|
+
</ConsentGate>
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The iframe is absent from the page until the visitor allows measurement. The
|
|
48
|
+
`placeholder` shows a message and a
|
|
49
|
+
[`ConsentDialogLink`](./components/consent-dialog-link.md)
|
|
50
|
+
until then. When the visitor withdraws measurement, React removes the iframe.
|
|
51
|
+
|
|
52
|
+
Pick the category that matches what the embed does. The example uses
|
|
53
|
+
measurement for YouTube because its player measures views. A map or chat
|
|
54
|
+
widget usually belongs under functionality or experience.
|
|
55
|
+
[ConsentGate](./components/consent-gate.md) documents the
|
|
56
|
+
built-in placeholder and every prop.
|
|
57
|
+
|
|
58
|
+
## Gate an iframe with the iframe blocker
|
|
59
|
+
|
|
60
|
+
`ConsentProvider` runs the iframe blocker by default. Give an iframe
|
|
61
|
+
`data-src` instead of `src`, and a `data-category`:
|
|
62
|
+
|
|
63
|
+
```html title="Markup from your CMS"
|
|
64
|
+
<iframe
|
|
65
|
+
data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
|
|
66
|
+
data-category="functionality"
|
|
67
|
+
title="Store map"
|
|
68
|
+
></iframe>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
c15t sets `src` from `data-src` when the category is allowed and removes `src`
|
|
72
|
+
when the category is withdrawn. It watches the page, so iframes added later
|
|
73
|
+
are handled too. Add `data-vendor` with a vendor ID to also block the iframe
|
|
74
|
+
while the visitor has turned that vendor off.
|
|
75
|
+
|
|
76
|
+
To turn the blocker off, set `iframeBlocker: false` in the provider `options`.
|
|
77
|
+
|
|
78
|
+
## Verify the embeds
|
|
79
|
+
|
|
80
|
+
Open the production build in a private window with DevTools open, under a
|
|
81
|
+
policy that asks for consent.
|
|
82
|
+
|
|
83
|
+
1. No iframe has a `src` pointing at `youtube-nocookie.com` or
|
|
84
|
+
`google.com/maps`, and the Network panel shows no request to them.
|
|
85
|
+
2. Allow the embed's category and save. The iframe loads.
|
|
86
|
+
3. Withdraw the category and save. The page reloads without the embed.
|
|
87
|
+
|
|
88
|
+
The [YouTube](../../integrations/youtube.md) and
|
|
89
|
+
[Google Maps](../../integrations/google-maps.md) guides cover sizing and titles.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hooks
|
|
3
|
+
description: Gate features and save consent choices in React components with the
|
|
4
|
+
focused hooks exported from c15t/react.
|
|
5
|
+
group: frameworks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Read only the state you need
|
|
9
|
+
|
|
10
|
+
Every hook reads the runtime from the nearest `ConsentProvider`, so call them
|
|
11
|
+
from components rendered inside that provider and import them from
|
|
12
|
+
`c15t/react`.
|
|
13
|
+
|
|
14
|
+
```tsx title="src/optional-feature.tsx"
|
|
15
|
+
import { useConsent } from 'c15t/react';
|
|
16
|
+
|
|
17
|
+
export function OptionalFeature() {
|
|
18
|
+
const allowed = useConsent('marketing');
|
|
19
|
+
return allowed ? <div>Optional marketing content</div> : null;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Call hooks inside the consent provider. `useConsent` reads effective permission,
|
|
24
|
+
which can be true under an opt-out policy without a recorded grant.
|
|
25
|
+
|
|
26
|
+
| Need | Hook |
|
|
27
|
+
| ----------------------------------- | ------------------------------------------ |
|
|
28
|
+
| One category's current permission | `useConsent(category)` |
|
|
29
|
+
| All current permissions | `useConsents()` |
|
|
30
|
+
| Recorded per-category choices | `useExplicitChoice()` |
|
|
31
|
+
| Whether one declared vendor may run | `useVendorAllowed(id)` |
|
|
32
|
+
| Whether a prompt is required | `usePromptRequirement()` |
|
|
33
|
+
| Active policy and resolution | `usePolicyRule()`, `usePolicyResolution()` |
|
|
34
|
+
| Save an explicit choice | `useSaveConsents()` |
|
|
35
|
+
| Open or close a consent surface | `useSetActiveUI()` |
|
|
36
|
+
| Acknowledge a notice | `useDismissNotice()` |
|
|
37
|
+
|
|
38
|
+
## Save from a visitor action
|
|
39
|
+
|
|
40
|
+
Render this component inside your existing `ConsentProvider`.
|
|
41
|
+
|
|
42
|
+
```tsx title="src/reject-button.tsx"
|
|
43
|
+
import { useSaveConsents } from 'c15t/react';
|
|
44
|
+
|
|
45
|
+
export function RejectButton() {
|
|
46
|
+
const save = useSaveConsents();
|
|
47
|
+
return (
|
|
48
|
+
<button type="button" onClick={() => void save('none')}>
|
|
49
|
+
Reject optional
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This is an isolated action example, not a complete policy-aware banner. Use the
|
|
56
|
+
stock UI or headless surface actions for a complete prompt. Do not call save on
|
|
57
|
+
mount to persist a state derived from permissions.
|
|
58
|
+
|
|
59
|
+
Use focused hooks for feature gates. For a complete custom prompt or preference
|
|
60
|
+
center, use `useHeadlessConsentUI()` for surface actions and `useConsentDraft()`
|
|
61
|
+
for staged choices, inside one `ConsentDraftProvider` so the save commits the
|
|
62
|
+
same draft. v3 has no `useConsentManager()`; the
|
|
63
|
+
[upgrade guide](../../upgrade-v3.md#replace-useconsentmanager) maps each of its
|
|
64
|
+
fields to a hook. Read [how consent works](../../concepts/how-consent-works.md#a-permission-is-not-a-recorded-choice) before
|
|
65
|
+
treating a callback as evidence of a choice.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: IAB TCF
|
|
3
|
+
description: Mount the IAB TCF banner and dialog inside a React ConsentProvider
|
|
4
|
+
and configure the CMP ID, policy and vendor data.
|
|
5
|
+
group: frameworks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Prepare the IAB configuration
|
|
9
|
+
|
|
10
|
+
Use the IAB integration when your application needs the Transparency and Consent
|
|
11
|
+
Framework. Configure an IAB policy, the correct CMP identity and vendor data for
|
|
12
|
+
your deployment. A demo CMP ID is not a production configuration.
|
|
13
|
+
|
|
14
|
+
A CMP ID is assigned to a company when it registers as a CMP with IAB Europe
|
|
15
|
+
([register.consensu.org/CMP](https://register.consensu.org/CMP)). Every TC
|
|
16
|
+
String names the CMP that wrote it, so an ID you did not register attributes
|
|
17
|
+
your users' consent records to someone else. Omitting it leaves the add-on
|
|
18
|
+
unmounted: no `__tcfapi`, and no TC String written.
|
|
19
|
+
|
|
20
|
+
Install `@c15t/iab` alongside your framework adapter, then mount the IAB
|
|
21
|
+
components inside an existing consent provider connected to Inth.
|
|
22
|
+
|
|
23
|
+
Import the components from `c15t/react/iab` and render them inside your
|
|
24
|
+
existing `ConsentProvider` from `c15t/react`. Import `c15t/react/iab/styles.css`
|
|
25
|
+
at your global stylesheet entry, after `c15t/react/styles.css`: the IAB
|
|
26
|
+
stylesheet holds only the IAB rules and uses the tokens from the base
|
|
27
|
+
stylesheet.
|
|
28
|
+
|
|
29
|
+
```tsx title="src/iab-consent.tsx"
|
|
30
|
+
import {
|
|
31
|
+
IABProvider,
|
|
32
|
+
IABConsentBanner,
|
|
33
|
+
IABConsentDialog,
|
|
34
|
+
} from 'c15t/react/iab';
|
|
35
|
+
|
|
36
|
+
export function IABConsent({ cmpId }: { cmpId: number }) {
|
|
37
|
+
return (
|
|
38
|
+
<IABProvider cmpId={cmpId}>
|
|
39
|
+
<IABConsentBanner />
|
|
40
|
+
<IABConsentDialog />
|
|
41
|
+
</IABProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Supply the CMP ID from your deployment's IAB configuration. Keep a persistent
|
|
47
|
+
preferences entry point through the surrounding consent provider.
|
|
48
|
+
|
|
49
|
+
## Resolve the policy before rendering
|
|
50
|
+
|
|
51
|
+
IAB components remain hidden until an applicable policy resolves. Forcing a
|
|
52
|
+
dialog open does not create a policy or vendor list. Inspect resolution and the
|
|
53
|
+
Global Vendor List response before changing rendering conditions.
|
|
54
|
+
|
|
55
|
+
IAB presentation follows `presentation.prompt.blocking` and
|
|
56
|
+
`presentation.preferences.blocking`. A blocking dialog needs focus management,
|
|
57
|
+
scroll locking and a backdrop together. See [migration](../../upgrade-v3.md) for
|
|
58
|
+
legacy option behavior.
|
|
59
|
+
|
|
60
|
+
## Verify vendor and purpose choices
|
|
61
|
+
|
|
62
|
+
Test category choices alongside vendor, purpose, legitimate-interest and special
|
|
63
|
+
feature controls used by your integration. Confirm the TC String and `__tcfapi`
|
|
64
|
+
reflect the saved state, and verify the vendor's actual requests. A category-only
|
|
65
|
+
banner test does not establish that the IAB flow works.
|
|
66
|
+
|
|
67
|
+
## Restrict how vendors use a purpose
|
|
68
|
+
|
|
69
|
+
Publisher restrictions override a vendor's declared use of one purpose. Pass
|
|
70
|
+
them to `IABProvider` as `publisherRestrictions`. The vendor IDs below are
|
|
71
|
+
placeholders; use vendors from your list whose declarations allow each
|
|
72
|
+
restriction.
|
|
73
|
+
|
|
74
|
+
```tsx title="src/iab-consent.tsx"
|
|
75
|
+
import {
|
|
76
|
+
IABProvider,
|
|
77
|
+
IABConsentBanner,
|
|
78
|
+
IABConsentDialog,
|
|
79
|
+
} from 'c15t/react/iab';
|
|
80
|
+
|
|
81
|
+
const publisherRestrictions = [
|
|
82
|
+
// Vendors 10 and 755 may not use purpose 4.
|
|
83
|
+
{ purposeId: 4, restrictionType: 0 as const, vendorIds: [10, 755] },
|
|
84
|
+
// Vendor 755 must use legitimate interest for purpose 7.
|
|
85
|
+
{ purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export function IABConsent({ cmpId }: { cmpId: number }) {
|
|
89
|
+
return (
|
|
90
|
+
<IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
|
|
91
|
+
<IABConsentBanner />
|
|
92
|
+
<IABConsentDialog />
|
|
93
|
+
</IABProvider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each restriction type has a requirement that depends on the vendor list:
|
|
99
|
+
|
|
100
|
+
| `restrictionType` | Meaning | Allowed when the vendor |
|
|
101
|
+
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------- |
|
|
102
|
+
| `0` | Purpose not allowed on any legal basis | declares the purpose for consent or legitimate interest |
|
|
103
|
+
| `1` | Consent required | declares the purpose for legitimate interest and lists it in `flexiblePurposes` |
|
|
104
|
+
| `2` | Legitimate interest required | declares the purpose for consent and lists it in `flexiblePurposes` |
|
|
105
|
+
|
|
106
|
+
Type `2` is never allowed for purposes 1, 3, 4, 5 and 6, which TCF allows
|
|
107
|
+
only with consent. TCF allows restrictions only in service-specific TC strings.
|
|
108
|
+
c15t always writes those, so the deprecated `isServiceSpecific` option has no
|
|
109
|
+
effect on restrictions.
|
|
110
|
+
c15t copies the restrictions when the CMP starts, so changing your array
|
|
111
|
+
afterwards has no effect.
|
|
112
|
+
|
|
113
|
+
c15t checks restrictions when the vendor list loads and again before each TC
|
|
114
|
+
String is encoded. Unsupported restrictions include type `3`, which the spec
|
|
115
|
+
reserves, a vendor or purpose missing from the list, and a vendor with two
|
|
116
|
+
restriction types for one purpose. They are never dropped. Loading IAB
|
|
117
|
+
settings, saving and generating a TC String all reject with a
|
|
118
|
+
`PublisherRestrictionError` exported by `@c15t/iab`. On a `createIAB` handle
|
|
119
|
+
these are `whenReady()`, `save()` and `generateTCString()`. No TC String is
|
|
120
|
+
written and no consent is recorded. Retrying `whenReady()` does not fetch
|
|
121
|
+
another vendor list. The error lasts until a new vendor list is set up: with an
|
|
122
|
+
explicit `gvl` option that never happens, so saving keeps failing even if the
|
|
123
|
+
kernel later holds a different list. When the CMP follows the kernel's list
|
|
124
|
+
instead, a replacement list is checked again and saving works once it accepts
|
|
125
|
+
the restrictions. When a replacement vendor list makes a restriction
|
|
126
|
+
unsupported, c15t also clears the TC authority confirmed under the previous
|
|
127
|
+
list, so gated scripts stop until the configuration is fixed. Whenever c15t
|
|
128
|
+
withdraws the authority it confirmed, it also removes the TC String from the
|
|
129
|
+
`euconsent-v2` cookie and localStorage entry. Because the check reads the
|
|
130
|
+
current vendor list, a vendor that stops declaring a purpose or its flexibility
|
|
131
|
+
makes a restriction invalid. Narrow a `vendors` allowlist and your restrictions
|
|
132
|
+
together.
|
|
133
|
+
|
|
134
|
+
Saved TC Strings carry the restrictions in their `PubRestrictions` section, and
|
|
135
|
+
`__tcfapi('getTCData')` reports them as `publisher.restrictions`, keyed by
|
|
136
|
+
purpose ID, then vendor ID. A stored TC String whose restrictions differ from
|
|
137
|
+
the current configuration is not restored. If the visitor's stored choice is
|
|
138
|
+
otherwise current, the banner opens again, as it does after a policy change,
|
|
139
|
+
and closes once the visitor saves. IAB gates that depend on the TC String stay
|
|
140
|
+
denied until then. c15t also removes the superseded TC String from the
|
|
141
|
+
`euconsent-v2` cookie and localStorage entry, so vendors reading standard
|
|
142
|
+
storage do not pick it up. An unchanged configuration restores the stored TC
|
|
143
|
+
String without asking.
|
|
144
|
+
When reading a string written under TCF policy version 2 or 3, c15t accepts
|
|
145
|
+
type `2` for purposes 3 to 6, which those versions allowed. Strings c15t
|
|
146
|
+
writes always follow the current policy.
|
|
147
|
+
|
|
148
|
+
The React, Vue, Svelte and `@c15t/browser/iab` preference centres list each
|
|
149
|
+
vendor under the legal basis the restrictions leave it. A vendor whose purpose
|
|
150
|
+
now requires legitimate interest gets an objection control instead of a
|
|
151
|
+
consent toggle, and a prohibited purpose no longer lists the vendor. A
|
|
152
|
+
purpose whose vendors all use legitimate interest has no consent switch,
|
|
153
|
+
because turning one off would change nothing the vendors rely on; its
|
|
154
|
+
objection control is the opt-out. Such a purpose does not decide its c15t
|
|
155
|
+
category: the category follows the purposes in it that the visitor can
|
|
156
|
+
consent to. Legitimate interest never grants a category on its own. If none of
|
|
157
|
+
a category's purposes has a consent basis, the category needs all of them
|
|
158
|
+
consented, which only Accept All does. Stack switches
|
|
159
|
+
cover only the purposes some vendor processes on consent. Display-model rows
|
|
160
|
+
expose this as `hasConsentBasis`. Custom preference UIs can call
|
|
161
|
+
`applyPublisherRestrictionsToGVL` from `@c15t/iab/headless`, or pass
|
|
162
|
+
`publisherRestrictions` to `processGVLForDialog`, to get the same vendor
|
|
163
|
+
declarations. The configured restrictions are on the kernel's IAB state as
|
|
164
|
+
`publisherRestrictions`.
|
|
165
|
+
|
|
166
|
+
Consent-gated scripts, network rules and iframes with a `vendorId` also apply
|
|
167
|
+
the confirmed restrictions for that vendor:
|
|
168
|
+
|
|
169
|
+
* Type `0` blocks a target that declares the purpose in `iabPurposes` or
|
|
170
|
+
`iabLegIntPurposes`.
|
|
171
|
+
* Type `1` makes a purpose declared in `iabLegIntPurposes` require purpose and
|
|
172
|
+
vendor consent. Legitimate interest no longer satisfies it.
|
|
173
|
+
* Type `2` makes a purpose declared in `iabPurposes` require purpose and vendor
|
|
174
|
+
legitimate interest. Consent no longer satisfies it.
|
|
175
|
+
|
|
176
|
+
Types `1` and `2` block the target when the vendor list does not mark the
|
|
177
|
+
purpose as flexible for that vendor. Targets without a `vendorId` ignore
|
|
178
|
+
restrictions. A target that uses only legitimate interest once restrictions
|
|
179
|
+
apply is not blocked by a refused category, since it needs no consent; its
|
|
180
|
+
legitimate interest signals and the visitor's objection decide. GPC and strict
|
|
181
|
+
scope still block it, and the category stays refused for scripts that depend on
|
|
182
|
+
it. Accept All grants the vendor signal a restriction moves a purpose to,
|
|
183
|
+
including legitimate interest for a vendor that declares none. Legitimate
|
|
184
|
+
interest a restriction introduces applies until the visitor objects: the
|
|
185
|
+
preference centres show it as allowed, and saving without touching it encodes
|
|
186
|
+
it as allowed. `introducedLegitimateInterest` from `@c15t/iab/headless` lists
|
|
187
|
+
those purposes and vendors for custom preference UIs.
|
|
188
|
+
|
|
189
|
+
## How the preference centre shows features
|
|
190
|
+
|
|
191
|
+
The stock IAB preference centres in React, Next.js, Vue, Svelte, Astro and the
|
|
192
|
+
script tag follow TCF 2.4 and Policies v5.0.b. Special purposes stay in their
|
|
193
|
+
locked section with a lock icon, because the visitor cannot object to them.
|
|
194
|
+
|
|
195
|
+
Features get their own section after the special purposes. It shows the IAB
|
|
196
|
+
standard text for features from the Global Vendor List's
|
|
197
|
+
`standardTexts.features`. If the list has no standard text, it shows the
|
|
198
|
+
`iab.preferenceCenter.features.description` translation. Each feature lists
|
|
199
|
+
its name, description, illustrations and the vendors that use it. The section has no switch, lock or vendor toggle, because
|
|
200
|
+
TCF forbids showing features next to a control that cannot be disabled.
|
|
201
|
+
|
|
202
|
+
The feature section follows the theme's spacing and typography tokens.
|
|
203
|
+
Its disclosure arrows are decorative and stay out of the accessibility tree.
|
|
204
|
+
|
|
205
|
+
A custom preference UI gets the same rows from
|
|
206
|
+
`resolveIABDialogDisplayModel` in `@c15t/iab/headless`. `essentialRows` holds
|
|
207
|
+
the special purposes only. `featureRows` holds the features, with
|
|
208
|
+
`locked: false` and `toggle: 'none'`, so render them without a control.
|
|
209
|
+
`featuresStandardText` is the standard text, or `null` when you should use the
|
|
210
|
+
translation.
|
|
211
|
+
|
|
212
|
+
## Consent scope
|
|
213
|
+
|
|
214
|
+
c15t stores TCF choices per site and browser, which the TCF calls
|
|
215
|
+
service-specific scope. It does not sync TC strings across devices, and every
|
|
216
|
+
TC string it writes has `IsServiceSpecific` set to `1`.
|
|
217
|
+
|
|
218
|
+
Do not use `/consent/check` or your own account data to skip the TCF banner on
|
|
219
|
+
another device. That turns the choice into multi-device scope, which the TCF
|
|
220
|
+
requires you to disclose in the first layer, and c15t does not show that
|
|
221
|
+
disclosure. The `isServiceSpecific` option is deprecated. c15t ignores it and
|
|
222
|
+
logs a warning when you pass `false`.
|