@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rendering and deployment
|
|
3
|
+
description: How c15t resolves consent in client-rendered React apps, React
|
|
4
|
+
Router framework mode, Remix and other server-rendered React apps without a
|
|
5
|
+
c15t adapter.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pick your rendering path
|
|
10
|
+
|
|
11
|
+
`c15t/react` resolves consent in the browser. That works for every React app,
|
|
12
|
+
including server-rendered ones. What changes is what the first HTML contains.
|
|
13
|
+
|
|
14
|
+
| Your app | Where consent resolves | Setup |
|
|
15
|
+
| ------------------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| Vite, or another client-rendered single-page app | Browser, after the bundle loads | [Quickstart](./quickstart.md) |
|
|
17
|
+
| Prerendered or static HTML | Browser, after the page loads | [Quickstart](./quickstart.md), see [static pages](#static-and-prerendered-pages) |
|
|
18
|
+
| React Router framework mode, Remix, or another server-rendered React app | Browser, after hydration | [Quickstart](./quickstart.md), see [server-rendered apps](#react-router-remix-and-other-server-rendered-apps) |
|
|
19
|
+
| Next.js or TanStack Start | Server, per request | That framework's guide in the [framework list](https://c15t.com/docs/frameworks) |
|
|
20
|
+
|
|
21
|
+
Pick the Next.js or TanStack Start adapter if you use those frameworks. They
|
|
22
|
+
read the visitor's cookie and location on the server, so the banner can be in
|
|
23
|
+
the first HTML. [Choose your setup](../../concepts/choose-your-setup.md#server-rendering-or-browser-rendering)
|
|
24
|
+
explains the difference in general terms.
|
|
25
|
+
|
|
26
|
+
## Client-rendered apps
|
|
27
|
+
|
|
28
|
+
The [quickstart](./quickstart.md) setup runs entirely in the
|
|
29
|
+
browser:
|
|
30
|
+
|
|
31
|
+
1. The page loads with no consent state. Every optional category is denied.
|
|
32
|
+
2. `ConsentProvider` requests `GET /init` from your backend URL. The backend
|
|
33
|
+
picks the visitor's policy from their location.
|
|
34
|
+
3. The provider applies the policy and any stored choice. It shows the banner
|
|
35
|
+
if the policy asks for one, and loads the scripts the visitor allowed.
|
|
36
|
+
|
|
37
|
+
Until step 3 finishes, gated scripts and `ConsentGate` embeds stay blocked. If
|
|
38
|
+
the backend request fails, they stay blocked and no banner shows.
|
|
39
|
+
|
|
40
|
+
## React Router, Remix and other server-rendered apps
|
|
41
|
+
|
|
42
|
+
React Router framework mode, Remix and similar frameworks have no c15t adapter.
|
|
43
|
+
Use the quickstart's `Consent` component and render it in your root component,
|
|
44
|
+
around the outlet. In React Router framework mode that is `app/root.tsx`.
|
|
45
|
+
|
|
46
|
+
`ConsentProvider` renders on the server without touching browser APIs, and the
|
|
47
|
+
browser takes over after hydration:
|
|
48
|
+
|
|
49
|
+
* The server HTML contains your page but no banner and no consent state.
|
|
50
|
+
* After hydration the browser requests `/init`, then shows the banner if the
|
|
51
|
+
policy asks for one.
|
|
52
|
+
* The server render and the first browser render agree, so hydration does not
|
|
53
|
+
warn or change a stored choice.
|
|
54
|
+
|
|
55
|
+
One detail differs from the Vite setup: import `c15t/react/styles.css` where
|
|
56
|
+
your framework loads global CSS, so the server HTML links it.
|
|
57
|
+
|
|
58
|
+
### Skip the browser's first init request
|
|
59
|
+
|
|
60
|
+
This is optional. `fetchSSRData` from `c15t/react/server` calls the backend's
|
|
61
|
+
`/init` from your server loader, forwarding the visitor's location and
|
|
62
|
+
language headers. Pass the result to the browser, wrap it in a promise and give
|
|
63
|
+
it to `hosted({ url, initialData })`. The provider's first init then uses that
|
|
64
|
+
response instead of a network request.
|
|
65
|
+
|
|
66
|
+
Know its limits before you add it:
|
|
67
|
+
|
|
68
|
+
* It saves the browser's round trip to the backend. It does not put the banner
|
|
69
|
+
in the server HTML; the banner still mounts after hydration.
|
|
70
|
+
* Every page view makes a server-to-server `/init` request. Do not cache the
|
|
71
|
+
loader response, because it contains one visitor's policy.
|
|
72
|
+
* It returns `undefined` when the request has no location or language headers,
|
|
73
|
+
and the browser then requests `/init` as usual.
|
|
74
|
+
* `mode` is read once, when the provider mounts. Create the `hosted()` mode once
|
|
75
|
+
in the browser rather than on every render.
|
|
76
|
+
* A relative `backendURL` resolves against the request's `host` header, over
|
|
77
|
+
`https` for a domain name and over `http` for `localhost`, an IP address or
|
|
78
|
+
a single-label host. `fetchSSRData` ignores `x-forwarded-host`,
|
|
79
|
+
`x-forwarded-proto` and `forwarded`, and does not send them to the backend.
|
|
80
|
+
Behind a proxy that overwrites those headers, pass
|
|
81
|
+
`trustForwardedHeaders: true` to use them.
|
|
82
|
+
* If your server answers requests for any `Host` header, such as a Node server
|
|
83
|
+
exposed directly or a proxy that forwards `Host` from its default virtual
|
|
84
|
+
host, pass `fetchSSRData` an absolute `backendURL`. A relative one resolves
|
|
85
|
+
against whatever `Host` the request sent.
|
|
86
|
+
|
|
87
|
+
### Render the banner in the server HTML
|
|
88
|
+
|
|
89
|
+
c15t has no framework-neutral helper for this. `ConsentProvider` accepts a
|
|
90
|
+
server-resolved `prefetch` state, but the Next.js and TanStack Start adapters
|
|
91
|
+
build that state from the request's cookie, headers and a cached policy
|
|
92
|
+
manifest. Building it by hand ties your app to internal shapes. If the banner
|
|
93
|
+
must be part of the first paint, use one of those adapters. Otherwise keep the
|
|
94
|
+
browser path.
|
|
95
|
+
|
|
96
|
+
## Static and prerendered pages
|
|
97
|
+
|
|
98
|
+
A prerendered page is the same HTML for every visitor, so it cannot contain one
|
|
99
|
+
visitor's consent. Use the quickstart setup unchanged. The browser talks to the
|
|
100
|
+
backend URL directly, so the static host needs no server route. Add the site's
|
|
101
|
+
origin to the trusted origins in your Inth project.
|
|
102
|
+
|
|
103
|
+
Do not cache a response that contains a visitor's consent in a CDN or a shared
|
|
104
|
+
page cache. [Data fetching](../../concepts/data-fetching.md) covers what is safe to
|
|
105
|
+
cache.
|
|
106
|
+
|
|
107
|
+
## Where the browser sends requests
|
|
108
|
+
|
|
109
|
+
With `hosted({ url })`, the browser calls two backend endpoints:
|
|
110
|
+
|
|
111
|
+
| Request | When |
|
|
112
|
+
| --------------------- | ------------------------------------------ |
|
|
113
|
+
| `GET {url}/init` | Once per page load, to resolve the policy |
|
|
114
|
+
| `POST {url}/subjects` | When the visitor accepts, rejects or saves |
|
|
115
|
+
|
|
116
|
+
Both go to the backend's origin, so it must list your site as a trusted origin.
|
|
117
|
+
[Data fetching](../../concepts/data-fetching.md) compares this with proxies and
|
|
118
|
+
custom transports.
|
|
119
|
+
|
|
120
|
+
## Check the rendering path
|
|
121
|
+
|
|
122
|
+
1. Open the page in a private window and view the page source. In a
|
|
123
|
+
server-rendered app the HTML contains your content and no element with
|
|
124
|
+
`data-testid="consent-banner-root"`.
|
|
125
|
+
2. In DevTools Network, filter by your backend URL. One `/init` request runs
|
|
126
|
+
after the JavaScript loads, or none if you pass `initialData`.
|
|
127
|
+
3. The banner appears once that request finishes. Vendor requests stay absent
|
|
128
|
+
until you allow their category.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripts
|
|
3
|
+
description: Load vendor scripts by consent category in a React app with
|
|
4
|
+
ConsentProvider, and clear stored data or reload the page when a visitor
|
|
5
|
+
withdraws consent.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Register vendor scripts
|
|
10
|
+
|
|
11
|
+
Pass vendor loaders to `ConsentProvider` through `options.scripts`. The
|
|
12
|
+
provider loads each script when its category is allowed and removes it when
|
|
13
|
+
the category is denied. The [quickstart](./quickstart.md)
|
|
14
|
+
registers PostHog and X Pixel this way:
|
|
15
|
+
|
|
16
|
+
```ts title="src/scripts.ts"
|
|
17
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
18
|
+
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
19
|
+
|
|
20
|
+
export const scripts = [
|
|
21
|
+
posthog({
|
|
22
|
+
id: 'phc_your_project_key',
|
|
23
|
+
initOptions: { cookieless_mode: 'never' },
|
|
24
|
+
loadMode: 'after-consent',
|
|
25
|
+
}),
|
|
26
|
+
xPixel({ pixelId: 'your-pixel-id' }),
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The provider in `src/consent.tsx` receives them:
|
|
31
|
+
|
|
32
|
+
```tsx title="src/consent.tsx"
|
|
33
|
+
import {
|
|
34
|
+
ConsentBanner,
|
|
35
|
+
ConsentDialog,
|
|
36
|
+
ConsentDialogLink,
|
|
37
|
+
ConsentProvider,
|
|
38
|
+
hosted,
|
|
39
|
+
} from 'c15t/react';
|
|
40
|
+
import type { ReactNode } from 'react';
|
|
41
|
+
|
|
42
|
+
import { scripts } from './scripts';
|
|
43
|
+
|
|
44
|
+
import 'c15t/react/styles.css';
|
|
45
|
+
|
|
46
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
47
|
+
|
|
48
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
49
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
50
|
+
{children}
|
|
51
|
+
<ConsentBanner />
|
|
52
|
+
<ConsentDialog />
|
|
53
|
+
<footer>
|
|
54
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
55
|
+
</footer>
|
|
56
|
+
</ConsentProvider>
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each helper from `@c15t/integrations` sets its own category and a stable `id`.
|
|
61
|
+
Remove every other loader for the same vendor, such as a `<script>` tag in
|
|
62
|
+
`index.html` or an SDK you initialize at module level, so the vendor loads
|
|
63
|
+
once and only through c15t. [Integrations](../../integrations/overview.md) lists
|
|
64
|
+
every helper, and [building integrations](../../integrations/building-integrations.md)
|
|
65
|
+
covers a vendor without one.
|
|
66
|
+
|
|
67
|
+
## What happens when consent changes
|
|
68
|
+
|
|
69
|
+
* **Allowed.** The script loads, or its SDK is started, the first time its
|
|
70
|
+
category is allowed.
|
|
71
|
+
* **Denied later.** The provider removes the script element. Code that already
|
|
72
|
+
ran keeps running, so after the visitor turns off a category they had
|
|
73
|
+
allowed, the provider reloads the page once the save finishes. Set
|
|
74
|
+
`reloadOnConsentRevoked: false` only if every gated vendor stops itself.
|
|
75
|
+
* **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode,
|
|
76
|
+
load before consent and pass the visitor's choice to the vendor. Read the
|
|
77
|
+
vendor's guide; a category on a script does not always mean zero requests.
|
|
78
|
+
|
|
79
|
+
## Embeds and other requests
|
|
80
|
+
|
|
81
|
+
Scripts cover vendor code c15t loads for you. For the rest:
|
|
82
|
+
|
|
83
|
+
* [Embeds](./embeds.md) keeps iframes out of the page with
|
|
84
|
+
`ConsentGate` or the iframe blocker.
|
|
85
|
+
* [Network blocker](./network-blocker.md) holds `fetch` and
|
|
86
|
+
XHR calls that match a rule until their category is allowed.
|
|
87
|
+
|
|
88
|
+
## Clear stored data after revocation
|
|
89
|
+
|
|
90
|
+
Removing a script does not delete the cookies or storage entries it wrote. Add
|
|
91
|
+
`clearOnRevocation` to the provider options to delete the entries you list for
|
|
92
|
+
each category when it is denied. The option is read once, when the provider
|
|
93
|
+
mounts. [Clear on revocation](./clear-on-revocation.md) covers
|
|
94
|
+
configuration and browser limits.
|
|
95
|
+
|
|
96
|
+
## Let visitors turn off one vendor
|
|
97
|
+
|
|
98
|
+
A visitor can allow marketing and still switch off one vendor in it. Pass
|
|
99
|
+
`vendors` to the provider options. Helpers from `@c15t/integrations` already carry
|
|
100
|
+
their vendor slug. See [vendor consent](./vendor-consent.md).
|
|
101
|
+
|
|
102
|
+
To send your own events only to allowed integrations, see
|
|
103
|
+
[send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
|
|
104
|
+
|
|
105
|
+
## Check the scripts
|
|
106
|
+
|
|
107
|
+
Open DevTools Network in a private window, filter by each vendor's domain and
|
|
108
|
+
reload.
|
|
109
|
+
|
|
110
|
+
1. Before a choice under an opt-in policy, no vendor script loads.
|
|
111
|
+
2. Allow one category. Only that category's vendors load.
|
|
112
|
+
3. Turn the category off. The page reloads and the vendor stays absent.
|
|
113
|
+
4. Reload again. The rejection holds.
|
|
114
|
+
|
|
115
|
+
[Verify consent](../../guides/verify-consent.md) has the full checklist.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Translations
|
|
3
|
+
description: Where c15t banner and dialog copy comes from in a React app, how
|
|
4
|
+
the browser picks the visitor's language, and how to override copy and switch
|
|
5
|
+
languages with the provider's i18n option and hooks.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Where the copy comes from
|
|
10
|
+
|
|
11
|
+
The banner, dialog, widget and gate placeholder read their text from one set
|
|
12
|
+
of translations for the visitor's language. Which set depends on the
|
|
13
|
+
provider's `mode`:
|
|
14
|
+
|
|
15
|
+
| Mode | Copy comes from |
|
|
16
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `hosted()` | The backend's `/init` response for the visitor's language, from the translations in your Inth project or your self-hosted backend's configuration, with your `i18n` messages for that language on top |
|
|
18
|
+
| `offline()` | Your `i18n` messages, merged over bundled English |
|
|
19
|
+
|
|
20
|
+
With a backend, edit wording in your Inth project first, so it changes without
|
|
21
|
+
a deploy. Code can override that copy in two places:
|
|
22
|
+
|
|
23
|
+
* `i18n` in the `ConsentProvider` options changes a message everywhere it
|
|
24
|
+
appears.
|
|
25
|
+
* Text props on `ConsentBanner` change one banner.
|
|
26
|
+
|
|
27
|
+
[Copy and translations](../../customization/translations.md) lists the message
|
|
28
|
+
keys and explains how code messages combine with the project's copy.
|
|
29
|
+
|
|
30
|
+
## How c15t picks the language
|
|
31
|
+
|
|
32
|
+
In `hosted()` mode the browser's `/init` request carries its own
|
|
33
|
+
`Accept-Language` header, and the backend answers with copy in that language.
|
|
34
|
+
When your project has no copy for it, the backend returns its fallback
|
|
35
|
+
language. Set `overrides.language` in the provider options to request a
|
|
36
|
+
specific language instead.
|
|
37
|
+
|
|
38
|
+
In `offline()` mode, `i18n.locale` picks the language, `en` by default. The
|
|
39
|
+
browser's language is not read.
|
|
40
|
+
|
|
41
|
+
The banner, dialog and widget set `dir` from the active language. Right-to-left
|
|
42
|
+
languages such as Arabic and Hebrew render with `dir="rtl"`, and a floating
|
|
43
|
+
banner you did not position yourself moves to the opposite side.
|
|
44
|
+
|
|
45
|
+
## Change the copy in code
|
|
46
|
+
|
|
47
|
+
Keep the messages in their own module:
|
|
48
|
+
|
|
49
|
+
```ts title="src/consent-i18n.ts"
|
|
50
|
+
import type { ConsentProviderOptions } from 'c15t/react';
|
|
51
|
+
|
|
52
|
+
// Keys you leave out keep the copy from your Inth project.
|
|
53
|
+
export const i18n = {
|
|
54
|
+
messages: {
|
|
55
|
+
de: {
|
|
56
|
+
common: { rejectAll: 'Optionale ablehnen' },
|
|
57
|
+
cookieBanner: { title: 'Cookies auf dieser Website' },
|
|
58
|
+
},
|
|
59
|
+
en: {
|
|
60
|
+
common: { rejectAll: 'Reject optional' },
|
|
61
|
+
cookieBanner: { title: 'Cookies on this site' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
} satisfies ConsentProviderOptions['i18n'];
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pass them to the provider in the file that renders it, such as
|
|
68
|
+
`src/consent.tsx` from the [quickstart](./quickstart.md):
|
|
69
|
+
|
|
70
|
+
```tsx title="src/consent.tsx"
|
|
71
|
+
<ConsentProvider options={{ mode, scripts, i18n }}>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For the visitor's language, your messages replace the backend's copy key by
|
|
75
|
+
key. Keys you leave out keep the project's wording, and messages for other
|
|
76
|
+
languages are not applied. In `offline()` mode c15t bundles only English, so
|
|
77
|
+
keys you leave out for another language show in English.
|
|
78
|
+
|
|
79
|
+
The provider reads `i18n` once, when it mounts. In development it logs a
|
|
80
|
+
warning if `i18n` changes later. Define the object at module level, as above,
|
|
81
|
+
rather than inside a component.
|
|
82
|
+
|
|
83
|
+
## Change one banner's copy
|
|
84
|
+
|
|
85
|
+
`ConsentBanner` takes `title`, `description`, `acceptButtonText`,
|
|
86
|
+
`rejectButtonText`, `customizeButtonText` and `dismissButtonText`. The props
|
|
87
|
+
replace the text in every language, so pass values in the page's language:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<ConsentBanner
|
|
91
|
+
title="Cookies on this site"
|
|
92
|
+
rejectButtonText="Reject optional"
|
|
93
|
+
/>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Switch the language at runtime
|
|
97
|
+
|
|
98
|
+
`useSetLanguage()` stores the new language, and `useInit()` resolves the policy
|
|
99
|
+
again, which fetches the copy for that language. Call both from your language
|
|
100
|
+
switcher:
|
|
101
|
+
|
|
102
|
+
```tsx title="src/language-switcher.tsx"
|
|
103
|
+
import { useInit, useSetLanguage, useSnapshot } from 'c15t/react';
|
|
104
|
+
|
|
105
|
+
const languages = [
|
|
106
|
+
{ code: 'en', label: 'English' },
|
|
107
|
+
{ code: 'de', label: 'Deutsch' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
export const LanguageSwitcher = () => {
|
|
111
|
+
const setLanguage = useSetLanguage();
|
|
112
|
+
const init = useInit();
|
|
113
|
+
const language = useSnapshot().translations?.language ?? 'en';
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<select
|
|
117
|
+
aria-label="Consent language"
|
|
118
|
+
value={language}
|
|
119
|
+
onChange={(event) => {
|
|
120
|
+
setLanguage(event.target.value);
|
|
121
|
+
// Setting the language alone keeps the current copy. Init fetches
|
|
122
|
+
// the policy and copy for the new language.
|
|
123
|
+
void init();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{languages.map(({ code, label }) => (
|
|
127
|
+
<option key={code} value={code}>
|
|
128
|
+
{label}
|
|
129
|
+
</option>
|
|
130
|
+
))}
|
|
131
|
+
</select>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Setting the language without calling `init()` changes nothing on screen. Your
|
|
137
|
+
`i18n` messages for the new language apply on top of the copy that arrives.
|
|
138
|
+
Render the switcher anywhere inside `ConsentProvider`.
|
|
139
|
+
|
|
140
|
+
Switching needs a backend. In `offline()` mode the copy stays in the
|
|
141
|
+
`i18n.locale` language.
|
|
142
|
+
|
|
143
|
+
If your site changes language through its own i18n library, you can instead
|
|
144
|
+
pass its locale as `overrides.language` in the provider options. When that
|
|
145
|
+
value changes, the provider resolves the policy again in the new language.
|
|
146
|
+
|
|
147
|
+
## Verify
|
|
148
|
+
|
|
149
|
+
1. Set your browser language to one your project translates, clear site data
|
|
150
|
+
and reload under a policy that shows a banner. DevTools Network shows the
|
|
151
|
+
`/init` request with that `Accept-Language` header, and the banner uses
|
|
152
|
+
that language.
|
|
153
|
+
2. Check that a message you set in `i18n` shows in the banner.
|
|
154
|
+
3. Switch the language with your switcher. A new `/init` request runs, and the
|
|
155
|
+
banner and dialog text change without a reload.
|
|
@@ -1,67 +1,112 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
2
|
+
title: Troubleshooting
|
|
3
|
+
description: Fix a missing banner, early vendor requests, unstyled components
|
|
4
|
+
and theme warnings in a React app that uses c15t/react.
|
|
4
5
|
group: frameworks
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## The /init request goes to your-project.inth.app
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
requires no prompt can all produce no banner for different reasons. A missing
|
|
12
|
-
stylesheet can also make rendered controls appear incorrectly.
|
|
10
|
+
**Check.** In DevTools Network, the `GET` request to `/init` goes to
|
|
11
|
+
`your-project.inth.app` and fails.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
the
|
|
16
|
-
|
|
13
|
+
**Fix.** Replace the placeholder URL in `hosted({ url })` in `src/consent.tsx`
|
|
14
|
+
with the backend URL from your Inth project, exactly as Inth shows it, then
|
|
15
|
+
rebuild.
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## There is no banner
|
|
19
18
|
|
|
20
|
-
Check
|
|
21
|
-
|
|
22
|
-
`consent-dialog-*.js`, which some cookie-annoyance lists block. Update the c15t
|
|
23
|
-
packages and rebuild the app. Current component modules use neutral filenames;
|
|
24
|
-
public component imports stay the same. This also covers Vite development
|
|
25
|
-
requests used by TanStack Start and other Vite integrations.
|
|
19
|
+
**Check.** In DevTools Network, find the `GET` request to `/init` on your
|
|
20
|
+
backend URL.
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
| What you see | Cause | Fix |
|
|
23
|
+
| ------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| No `/init` request | `ConsentProvider` is not mounted, or an error stopped `src/consent.tsx` from rendering | Check the console for errors and render one provider at the app root, as in the [quickstart](./quickstart.md) |
|
|
25
|
+
| `/init` fails with a CORS error | Your site's origin is not trusted | Add the exact origin, including port in development, to the trusted origins in your Inth project |
|
|
26
|
+
| `/init` returns 200 | The policy for this visitor asks for no banner, or a stored choice is still valid | Inspect the active policy with the component below, or clear site data |
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
A policy with `prompt: 'none'` shows no banner by design, and so does a
|
|
29
|
+
returning visitor with a valid stored choice. [Policies](../../concepts/policies.md)
|
|
30
|
+
explains why a region may not show one.
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
for that vendor. Remove unconditional script tags, framework analytics plugins
|
|
35
|
-
and duplicate tag-manager entries. c15t's script registration only controls the
|
|
36
|
-
scripts registered with it.
|
|
32
|
+
## Inspect the active policy
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
is no request at all, do not use an always-loaded helper unchanged.
|
|
34
|
+
Render this component inside `ConsentProvider`. It reads state and records no
|
|
35
|
+
choice:
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
```tsx title="src/consent-policy-debug.tsx"
|
|
38
|
+
import { useSnapshot } from 'c15t/react';
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Do not "repair" persistence by saving permissions automatically on mount.
|
|
40
|
+
export function ConsentPolicyDebug() {
|
|
41
|
+
const snapshot = useSnapshot();
|
|
42
|
+
const resolution = snapshot.resolution;
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
return (
|
|
45
|
+
<pre>
|
|
46
|
+
{JSON.stringify(
|
|
47
|
+
{
|
|
48
|
+
status: resolution.status,
|
|
49
|
+
policy: resolution.status === 'matched' ? resolution.policy.id : null,
|
|
50
|
+
prompt: snapshot.promptRequirement,
|
|
51
|
+
permissions: snapshot.effectivePermissions,
|
|
52
|
+
location: snapshot.location,
|
|
53
|
+
privacySignals: snapshot.privacySignals,
|
|
54
|
+
},
|
|
55
|
+
null,
|
|
56
|
+
2
|
|
57
|
+
)}
|
|
58
|
+
</pre>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
inputs. A module-level mutable runtime on a server can share one visitor's state
|
|
54
|
-
with another request; create request-owned state instead.
|
|
63
|
+
The component re-renders on every consent change. Remove it after you finish.
|
|
64
|
+
For a feature gate, use `useConsent('marketing')` or the category you need.
|
|
55
65
|
|
|
56
|
-
##
|
|
66
|
+
## A vendor loads before the visitor chooses
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
production output with a static file server, not the framework dev server.
|
|
68
|
+
**Check.** Search `index.html`, your entry module and any tag manager for the
|
|
69
|
+
vendor's script or SDK initializer.
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
**Fix.** Remove every loader except the one in `options.scripts`. c15t only
|
|
72
|
+
gates scripts registered with it, and the network blocker cannot stop code that
|
|
73
|
+
runs before the provider renders. [Scripts and embeds](./scripts.md)
|
|
74
|
+
covers the options.
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
## The banner has no styles
|
|
77
|
+
|
|
78
|
+
**Check.** In DevTools Elements, the banner has classes but no rules apply.
|
|
79
|
+
|
|
80
|
+
**Fix.** Import `c15t/react/styles.css` once in the module that renders the
|
|
81
|
+
provider. With Tailwind 3, keep that import above the `@tailwind` directives
|
|
82
|
+
and add `c15t/postcss-tailwind3` before `tailwindcss` in your PostCSS
|
|
83
|
+
config. Without the plugin, Tailwind 3 drops c15t's rules or fails the build
|
|
84
|
+
with `` `@layer components` is used but no matching `@tailwind components` directive is present ``.
|
|
85
|
+
See [Tailwind CSS](../../customization/tailwind.md#set-up-tailwind-css-3).
|
|
86
|
+
|
|
87
|
+
## The console warns that theme tokens are no longer turned into CSS
|
|
88
|
+
|
|
89
|
+
**Check.** `options.theme` holds colors, radius or other tokens, and the page
|
|
90
|
+
has no `c15t-theme` style element. The provider only reads `consentActions`
|
|
91
|
+
and slot styles from `theme`.
|
|
92
|
+
|
|
93
|
+
**Fix.** Move the tokens to your CSS or render `ConsentTheme`, as shown in
|
|
94
|
+
[customize](./customize.md#change-colors-fonts-and-radius).
|
|
95
|
+
|
|
96
|
+
## The banner is missing from server-rendered HTML
|
|
97
|
+
|
|
98
|
+
**Check.** Your app uses React Router framework mode, Remix or another
|
|
99
|
+
server-rendered setup without a c15t adapter. There this is expected: the
|
|
100
|
+
browser resolves consent after hydration.
|
|
101
|
+
|
|
102
|
+
**Fix.** None is needed for consent to work.
|
|
103
|
+
[Rendering](./rendering.md#react-router-remix-and-other-server-rendered-apps)
|
|
104
|
+
explains the options.
|
|
105
|
+
|
|
106
|
+
## More help
|
|
107
|
+
|
|
108
|
+
[Troubleshoot consent](../../guides/troubleshooting.md) covers problems shared by
|
|
109
|
+
every framework, such as a missing banner, analytics that load before a choice,
|
|
110
|
+
imports that fail because npm installed c15t v2, choices that disappear on
|
|
111
|
+
reload, server HTML that differs from the browser, static builds that fail and
|
|
112
|
+
content blockers that hide the consent UI.
|