@c15t/nextjs 2.1.0 → 2.2.1
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 +155 -0
- package/README.md +31 -15
- package/client/components/integrations.js +3 -0
- package/dist/components/integrations/index.cjs +1 -0
- package/dist/components/integrations/index.js +1 -0
- package/dist/headless.cjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/libs/browser-initial-data.cjs +1 -1
- package/dist/libs/initial-data.cjs +1 -1
- package/dist/styles.tw3.css +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist-types/components/integrations/index.d.ts +1 -0
- package/dist-types/libs/browser-initial-data.d.ts +1 -1
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +155 -73
- package/docs/{building-headless-components.md → frameworks/next/building-headless-components.md} +3 -1
- package/docs/{callbacks.md → frameworks/next/callbacks.md} +3 -1
- package/docs/{components → frameworks/next/components}/consent-banner.md +10 -21
- package/docs/{components → frameworks/next/components}/consent-dialog-link.md +8 -5
- package/docs/frameworks/next/components/consent-dialog-trigger.md +202 -0
- package/docs/{components → frameworks/next/components}/consent-dialog.md +7 -15
- package/docs/frameworks/next/components/consent-manager-provider.md +327 -0
- package/docs/{components → frameworks/next/components}/consent-widget.md +8 -6
- package/docs/{components → frameworks/next/components}/dev-tools.md +9 -6
- package/docs/{components → frameworks/next/components}/frame.md +8 -9
- package/docs/{concepts → frameworks/next/concepts}/client-modes.md +4 -2
- package/docs/{concepts → frameworks/next/concepts}/consent-categories.md +1 -0
- package/docs/{concepts → frameworks/next/concepts}/consent-models.md +1 -0
- package/docs/{concepts → frameworks/next/concepts}/cookie-management.md +3 -3
- package/docs/{concepts → frameworks/next/concepts}/glossary.md +1 -0
- package/docs/{concepts → frameworks/next/concepts}/initialization-flow.md +1 -0
- package/docs/{concepts → frameworks/next/concepts}/policy-packs.md +1 -0
- package/docs/{headless.md → frameworks/next/headless.md} +3 -1
- package/docs/{hooks → frameworks/next/hooks}/use-color-scheme.md +1 -0
- package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/checking-consent.md +3 -1
- package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/location-info.md +3 -1
- package/docs/frameworks/next/hooks/use-consent-manager/overview.md +185 -0
- package/docs/{hooks → frameworks/next/hooks}/use-consent-manager/setting-consent.md +3 -1
- package/docs/{hooks → frameworks/next/hooks}/use-draggable.md +3 -1
- package/docs/{hooks → frameworks/next/hooks}/use-focus-trap.md +1 -0
- package/docs/{hooks → frameworks/next/hooks}/use-reduced-motion.md +3 -1
- package/docs/{hooks → frameworks/next/hooks}/use-ssr-status.md +6 -5
- package/docs/{hooks → frameworks/next/hooks}/use-text-direction.md +1 -0
- package/docs/frameworks/next/hooks/use-translations.md +55 -0
- package/docs/{iab → frameworks/next/iab}/consent-banner.md +9 -12
- package/docs/{iab → frameworks/next/iab}/consent-dialog.md +9 -14
- package/docs/{iab → frameworks/next/iab}/overview.md +4 -2
- package/docs/{iab → frameworks/next/iab}/use-gvl-data.md +3 -2
- package/docs/{iframe-blocking.md → frameworks/next/iframe-blocking.md} +8 -9
- package/docs/{internationalization.md → frameworks/next/internationalization.md} +13 -3
- package/docs/{network-blocker.md → frameworks/next/network-blocker.md} +13 -51
- package/docs/{optimization.md → frameworks/next/optimization.md} +5 -4
- package/docs/{policy-packs.md → frameworks/next/policy-packs.md} +3 -1
- package/docs/{quickstart.md → frameworks/next/quickstart.md} +26 -42
- package/docs/{script-loader.md → frameworks/next/script-loader.md} +72 -90
- package/docs/{server-side.md → frameworks/next/server-side.md} +12 -15
- package/docs/{styling → frameworks/next/styling}/classnames.md +3 -1
- package/docs/{styling → frameworks/next/styling}/color-scheme.md +3 -1
- package/docs/frameworks/next/styling/css-variables.md +53 -0
- package/docs/frameworks/next/styling/overview.md +299 -0
- package/docs/frameworks/next/styling/slots.md +94 -0
- package/docs/{styling → frameworks/next/styling}/tailwind.md +3 -1
- package/docs/frameworks/next/styling/tokens.md +156 -0
- package/docs/{troubleshooting.md → frameworks/next/troubleshooting.md} +3 -1
- package/docs/integrations/adobe-analytics.md +160 -0
- package/docs/integrations/ahrefs-analytics.md +15 -82
- package/docs/integrations/amplitude.md +230 -0
- package/docs/integrations/building-integrations.md +15 -181
- package/docs/integrations/clearbit.md +130 -0
- package/docs/integrations/cloudflare-web-analytics.md +15 -83
- package/docs/integrations/crisp.md +13 -87
- package/docs/integrations/databuddy.md +16 -85
- package/docs/integrations/fathom-analytics.md +15 -86
- package/docs/integrations/google-maps.md +263 -0
- package/docs/integrations/google-tag-manager.md +18 -86
- package/docs/integrations/google-tag.md +15 -84
- package/docs/integrations/heap.md +219 -0
- package/docs/integrations/hightouch.md +199 -0
- package/docs/integrations/hotjar.md +15 -83
- package/docs/integrations/intercom.md +13 -83
- package/docs/integrations/linkedin-insights.md +15 -82
- package/docs/integrations/logrocket.md +175 -0
- package/docs/integrations/matomo-analytics.md +13 -88
- package/docs/integrations/meta-pixel.md +33 -316
- package/docs/integrations/microsoft-clarity.md +15 -83
- package/docs/integrations/microsoft-uet.md +16 -83
- package/docs/integrations/mixpanel-analytics.md +15 -83
- package/docs/integrations/overview.md +55 -6
- package/docs/integrations/pirsch.md +142 -0
- package/docs/integrations/plausible-analytics.md +14 -97
- package/docs/integrations/posthog.md +26 -98
- package/docs/integrations/promptwatch.md +16 -82
- package/docs/integrations/reddit-pixel.md +27 -122
- package/docs/integrations/rudderstack.md +271 -0
- package/docs/integrations/rybbit-analytics.md +15 -94
- package/docs/integrations/segment.md +15 -82
- package/docs/integrations/snapchat-pixel.md +21 -102
- package/docs/integrations/tiktok-pixel.md +13 -81
- package/docs/integrations/umami-analytics.md +16 -88
- package/docs/integrations/vercel-analytics.md +13 -85
- package/docs/integrations/x-pixel.md +27 -102
- package/docs/integrations/youtube.md +193 -0
- package/docs/shared/concepts/client-modes.md +103 -0
- package/docs/shared/concepts/consent-categories.md +41 -0
- package/docs/shared/concepts/consent-models.md +72 -0
- package/docs/shared/concepts/cookie-management.md +88 -0
- package/docs/shared/concepts/glossary.md +24 -0
- package/docs/shared/concepts/initialization-flow.md +105 -0
- package/docs/shared/concepts/policy-packs.md +225 -0
- package/docs/shared/react/components/consent-banner.md +242 -0
- package/docs/shared/react/components/consent-dialog-link.md +45 -0
- package/docs/shared/react/components/consent-dialog-trigger.md +185 -0
- package/docs/shared/react/components/consent-dialog.md +119 -0
- package/docs/shared/react/components/consent-manager-provider.md +225 -0
- package/docs/shared/react/components/consent-widget.md +121 -0
- package/docs/shared/react/components/dev-tools.md +81 -0
- package/docs/shared/react/components/frame.md +52 -0
- package/docs/shared/react/guides/building-headless-components.md +110 -0
- package/docs/shared/react/guides/callbacks.md +89 -0
- package/docs/shared/react/guides/headless.md +31 -0
- package/docs/shared/react/guides/iframe-blocking.md +65 -0
- package/docs/shared/react/guides/internationalization.md +123 -0
- package/docs/shared/react/guides/network-blocker.md +72 -0
- package/docs/shared/react/guides/optimization.md +44 -0
- package/docs/shared/react/guides/policy-packs.md +173 -0
- package/docs/shared/react/guides/script-loader.md +311 -0
- package/docs/shared/react/hooks/use-color-scheme.md +31 -0
- package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +95 -0
- package/docs/shared/react/hooks/use-consent-manager/location-info.md +96 -0
- package/docs/shared/react/hooks/use-consent-manager/overview.md +74 -0
- package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +93 -0
- package/docs/shared/react/hooks/use-draggable.md +30 -0
- package/docs/shared/react/hooks/use-focus-trap.md +20 -0
- package/docs/shared/react/hooks/use-reduced-motion.md +33 -0
- package/docs/shared/react/hooks/use-ssr-status.md +16 -0
- package/docs/shared/react/hooks/use-text-direction.md +38 -0
- package/docs/shared/react/hooks/use-translations.md +15 -0
- package/docs/shared/react/iab/consent-banner.md +60 -0
- package/docs/shared/react/iab/consent-dialog.md +76 -0
- package/docs/shared/react/iab/overview.md +80 -0
- package/docs/shared/react/iab/use-gvl-data.md +21 -0
- package/docs/shared/react/styling/classnames.md +93 -0
- package/docs/shared/react/styling/color-scheme.md +35 -0
- package/docs/shared/react/styling/css-variables.md +53 -0
- package/docs/shared/react/styling/overview.md +261 -0
- package/docs/shared/react/styling/slots.md +93 -0
- package/docs/shared/react/styling/stylesheet-entrypoint.md +8 -0
- package/docs/shared/react/styling/tailwind.md +88 -0
- package/docs/shared/react/styling/tokens.md +155 -0
- package/docs/shared/troubleshooting.md +82 -0
- package/package.json +19 -10
- package/readme.json +14 -8
- package/docs/ai-agents.md +0 -111
- package/docs/components/consent-dialog-trigger.md +0 -103
- package/docs/components/consent-manager-provider.md +0 -425
- package/docs/hooks/use-consent-manager/overview.md +0 -420
- package/docs/hooks/use-translations.md +0 -118
- package/docs/styling/css-variables.md +0 -92
- package/docs/styling/overview.md +0 -457
- package/docs/styling/slots.md +0 -128
- package/docs/styling/tokens.md +0 -216
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Building Headless Components
|
|
3
|
+
description: Reference page for building headless components.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
Building custom consent UI is easier now because c15t exposes multiple layers of policy-aware primitives instead of forcing you to reconstruct banner rules by hand.
|
|
7
|
+
|
|
8
|
+
Think of customization as a ladder:
|
|
9
|
+
|
|
10
|
+
* stock component props for the shortest path
|
|
11
|
+
* `ConsentBanner.PolicyActions` and `ConsentWidget.PolicyActions` when you want custom structure but still want c15t to resolve policy-aware actions
|
|
12
|
+
* `useHeadlessConsentUI()` when you need fully manual action rendering, custom controls, or non-standard flow
|
|
13
|
+
|
|
14
|
+
> ⚠️ **Warning:**
|
|
15
|
+
> Headless is the last step in the customization ladder. Use this guide only when pre-built components, tokens, slots, compound components, and noStyle are no longer sufficient.
|
|
16
|
+
|
|
17
|
+
The headless stack underneath that is:
|
|
18
|
+
|
|
19
|
+
* `useHeadlessConsentUI()` for policy-aware banner/dialog actions, ordering, layout, and primary actions hints
|
|
20
|
+
* `@c15t/ui/utils` for the pure policy-action helpers that framework packages build on
|
|
21
|
+
* `useConsentManager()` for runtime state, categories, selected consent state, and policy metadata
|
|
22
|
+
* `useTranslations()` for the resolved copy
|
|
23
|
+
* `offlinePolicy.policyPacks` for offline previews that behave like backend policy resolution
|
|
24
|
+
|
|
25
|
+
The split is intentional: `@c15t/ui` owns pure policy-action resolution, while the framework hooks own visibility, consent mutations, and reactive state.
|
|
26
|
+
|
|
27
|
+
> ℹ️ **Info:**
|
|
28
|
+
> This guide is about building your own components while still respecting resolved policy-pack behavior. For the general headless overview, see Headless Mode.
|
|
29
|
+
|
|
30
|
+
## Choose the Smallest Layer That Solves the Job
|
|
31
|
+
|
|
32
|
+
Start with the smallest API surface that still gives you the behavior you need:
|
|
33
|
+
|
|
34
|
+
* Stay with stock components when you only need theming, spacing, copy, or legal-link changes
|
|
35
|
+
* Use `ConsentBanner.PolicyActions` or `ConsentWidget.PolicyActions` when you want a custom compound-component layout but still want grouped actions, ordering, and primary emphasis to come from policy
|
|
36
|
+
* Add `renderAction` when the grouping is still correct but you want to remap actions to stock c15t button compounds
|
|
37
|
+
* Reach for `useHeadlessConsentUI()` only when you need custom button elements, need to map `actionGroups` yourself, wire non-button controls, or coordinate the consent UI with a more custom state machine
|
|
38
|
+
|
|
39
|
+
This order matters because every step down the ladder gives you more control, but also makes it easier for your UI to drift away from the resolved policy if you stop using the provided state.
|
|
40
|
+
|
|
41
|
+
## Before You Build Headless UI
|
|
42
|
+
|
|
43
|
+
Do not use headless mode for problems that are still inside the stock component model:
|
|
44
|
+
|
|
45
|
+
* Use `layout`, `direction`, `primaryButton`, and `legalLinks` before you rebuild banner markup
|
|
46
|
+
* Use `theme.consentActions` before you swap out stock actions
|
|
47
|
+
* Use tokens such as `colors.surface` and `colors.surfaceHover` before raw CSS overrides
|
|
48
|
+
* Use slots such as `consentBannerCard`, `consentBannerFooter`, and `consentDialogCard` before compound components
|
|
49
|
+
* Use `ConsentManagerProvider.options.i18n` before rebuilding UI just to change text
|
|
50
|
+
|
|
51
|
+
A good rule: if the stock banner or dialog structure is still correct, you probably do not need headless mode.
|
|
52
|
+
|
|
53
|
+
## What the Headless Tooling Gives You
|
|
54
|
+
|
|
55
|
+
The main win is that your custom UI can stay aligned with policy packs without duplicating policy logic in your components.
|
|
56
|
+
|
|
57
|
+
`useHeadlessConsentUI()` already resolves:
|
|
58
|
+
|
|
59
|
+
* which actions are allowed
|
|
60
|
+
* the order those actions should render in
|
|
61
|
+
* grouped actions from policy `layout`
|
|
62
|
+
* layout `direction` (`row` or `column`)
|
|
63
|
+
* the primary actions
|
|
64
|
+
* UI profile and scroll-lock hints
|
|
65
|
+
* whether the banner or dialog should currently be visible
|
|
66
|
+
|
|
67
|
+
The hook also gives you the policy-aware action helpers you are expected to call:
|
|
68
|
+
|
|
69
|
+
* `performBannerAction('accept' | 'reject')`
|
|
70
|
+
* `performDialogAction('accept' | 'reject')`
|
|
71
|
+
* `saveCustomPreferences()` for the dialog `customize` action
|
|
72
|
+
* `openDialog()`, `openBanner()`, and `closeUI()` for surface visibility
|
|
73
|
+
|
|
74
|
+
That means your component mostly focuses on markup and design-system concerns instead of re-implementing policy interpretation.
|
|
75
|
+
|
|
76
|
+
For most compound-component layouts, start with `ConsentBanner.PolicyActions` or `ConsentWidget.PolicyActions`. They render stock c15t buttons and translations by default, and `renderAction` is only needed when you want to override which stock compound renders for each action. Reach for manual `actionGroups` mapping when you need action rendering that no longer fits the stock button compounds.
|
|
77
|
+
|
|
78
|
+
## What Headless Is Not For
|
|
79
|
+
|
|
80
|
+
Headless mode is not the recommended path for:
|
|
81
|
+
|
|
82
|
+
* changing the banner footer background
|
|
83
|
+
* rounding the stock banner card
|
|
84
|
+
* restyling stock banner or dialog buttons
|
|
85
|
+
* changing consent copy
|
|
86
|
+
|
|
87
|
+
Those should stay in the pre-built stack with tokens, slots, `theme.consentActions`, and provider `i18n`.
|
|
88
|
+
|
|
89
|
+
## What a Policy-Aware Headless Component Should Respect
|
|
90
|
+
|
|
91
|
+
When you build custom banner or dialog components, make sure they use:
|
|
92
|
+
|
|
93
|
+
* `activeUI` or `banner.isVisible` / `dialog.isVisible` for visibility
|
|
94
|
+
* `allowedActions`, `actionGroups`, and `primaryActions` instead of hard-coding buttons
|
|
95
|
+
* `primaryActions` for visual emphasis
|
|
96
|
+
* `consentCategories` when deciding which category toggles to render
|
|
97
|
+
* `policyDecision` when you want to debug why a specific UI state was chosen
|
|
98
|
+
|
|
99
|
+
If you ignore those values, your custom UI can drift away from the resolved policy pack even though the underlying consent engine is configured correctly.
|
|
100
|
+
|
|
101
|
+
## Validation and Testing
|
|
102
|
+
|
|
103
|
+
If you are building a reusable headless component library, validate your rendered UI against the resolved runtime policy in tests.
|
|
104
|
+
|
|
105
|
+
The core package exposes:
|
|
106
|
+
|
|
107
|
+
* `getEffectivePolicy(initData)` to read the resolved policy from `/init`
|
|
108
|
+
* `validateUIAgainstPolicy({ policy, state })` to detect mismatches such as wrong actions, layout, or mode
|
|
109
|
+
|
|
110
|
+
This is useful when your design system renders custom button arrangements and you want tests to catch policy drift early.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Callbacks
|
|
3
|
+
description: Reference page for callbacks.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
Callbacks let you run custom code at key points in the consent lifecycle. Define them in the provider or runtime `callbacks` option, or register them dynamically after initialization.
|
|
7
|
+
|
|
8
|
+
For analytics SDKs and other change-only integrations, prefer `subscribeToConsentChanges()` or `onConsentChanged`. Use `onConsentSet` when you want the broader lifecycle signal, including initialization, automatic defaults, and replay-aware registration.
|
|
9
|
+
|
|
10
|
+
## Choose the Right Surface
|
|
11
|
+
|
|
12
|
+
|Surface|Replays when registered late?|Fires on init / hydration / auto-grants?|Best for|
|
|
13
|
+
|--|--|--|--|
|
|
14
|
+
|`onBannerFetched`|Yes, via `setCallback('onBannerFetched', ...)` after init|Yes|Logging resolved policy, location, and translations|
|
|
15
|
+
|`onConsentSet`|Yes, via `setCallback('onConsentSet', ...)`|Yes|Broad lifecycle hooks, debugging, and integrations that want the latest full state regardless of how it was reached|
|
|
16
|
+
|`onConsentChanged`|No|No|Declarative change-only integrations|
|
|
17
|
+
|`subscribeToConsentChanges()`|No|No|Canonical change-only subscriptions after mount|
|
|
18
|
+
|
|
19
|
+
> ℹ️ **Info:**
|
|
20
|
+
> Script.onConsentChange is a script-scoped lifecycle hook. It is not the global consent change API for analytics SDKs or other app-wide integrations.
|
|
21
|
+
|
|
22
|
+
## Available Callbacks
|
|
23
|
+
|
|
24
|
+
### `onBannerFetched`
|
|
25
|
+
|
|
26
|
+
Called when the consent banner data is fetched from the backend (or loaded from SSR data). The payload includes jurisdiction info, location data, and resolved translations.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
onBannerFetched: ({ jurisdiction, location, translations }) => {
|
|
30
|
+
// jurisdiction: 'GDPR' | 'CCPA' | { code: 'GDPR', message: '...' } | ...
|
|
31
|
+
// location: { countryCode: 'DE', regionCode: 'BY' }
|
|
32
|
+
// translations: { language: 'de', translations: {...} }
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `onConsentSet`
|
|
37
|
+
|
|
38
|
+
Called whenever c15t broadly settles consent state: store initialization, automatic defaults during init, explicit saves, and replay via `setCallback('onConsentSet', ...)`.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
onConsentSet: ({ preferences }) => {
|
|
42
|
+
// preferences: { necessary: true, measurement: true, marketing: false, ... }
|
|
43
|
+
console.log('Latest consent state:', preferences);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `onConsentChanged`
|
|
48
|
+
|
|
49
|
+
Called only after an explicit `saveConsents()` or `setConsent()` that actually changes the saved consent state. It never fires on store creation, hydration, automatic grants, unchanged saves, or `setCallback('onConsentChanged', ...)`.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
onConsentChanged: ({
|
|
53
|
+
preferences,
|
|
54
|
+
previousPreferences,
|
|
55
|
+
allowedCategories,
|
|
56
|
+
deniedCategories,
|
|
57
|
+
previousAllowedCategories,
|
|
58
|
+
previousDeniedCategories,
|
|
59
|
+
}) => {
|
|
60
|
+
analytics.syncConsent({
|
|
61
|
+
allowedCategories,
|
|
62
|
+
deniedCategories,
|
|
63
|
+
previousAllowedCategories,
|
|
64
|
+
previousDeniedCategories,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `onError`
|
|
70
|
+
|
|
71
|
+
Called when an error occurs during consent operations (e.g., API request failure). If no `onError` callback is provided, errors are logged to `console.error`.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
onError: ({ error }) => {
|
|
75
|
+
// error: string describing what went wrong
|
|
76
|
+
Sentry.captureMessage(`Consent error: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `onBeforeConsentRevocationReload`
|
|
81
|
+
|
|
82
|
+
Called synchronously before the page reloads due to consent revocation. This is your last chance to run cleanup before the reload. Keep this callback fast - avoid async operations.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
onBeforeConsentRevocationReload: ({ preferences }) => {
|
|
86
|
+
// Flush any pending data
|
|
87
|
+
navigator.sendBeacon('/api/flush', JSON.stringify({ session: sessionId }));
|
|
88
|
+
}
|
|
89
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Headless
|
|
3
|
+
description: Reference page for headless.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
c15t's headless mode means using the hooks (`useConsentManager`, `useTranslations`, etc.) without any pre-built UI components. This gives you complete control over the consent experience.
|
|
7
|
+
|
|
8
|
+
Before you go headless, walk the customization ladder in order:
|
|
9
|
+
|
|
10
|
+
1. **Pre-built components** - Use provider options, component props, tokens, slots, and `theme.consentActions`
|
|
11
|
+
2. **Compound components** - Rearrange c15t primitives when the markup order must change
|
|
12
|
+
3. **`noStyle`** - Keep c15t structure but replace its styling
|
|
13
|
+
4. **Headless** - Use only hooks and build the entire UI yourself
|
|
14
|
+
|
|
15
|
+
## When to Go Headless
|
|
16
|
+
|
|
17
|
+
Go headless when:
|
|
18
|
+
|
|
19
|
+
* Your design system requires complete control over markup
|
|
20
|
+
* You need a consent flow that doesn't fit the banner/dialog pattern
|
|
21
|
+
* You want to embed consent choices inline rather than as overlays
|
|
22
|
+
|
|
23
|
+
Use a lower-power tool instead when:
|
|
24
|
+
|
|
25
|
+
* The component structure works but the styling doesn't -> use tokens, slots, or `noStyle`
|
|
26
|
+
* You only need to rearrange existing c15t parts -> use compound components
|
|
27
|
+
* You want to change copy -> use `ConsentManagerProvider.options.i18n`
|
|
28
|
+
* You only need to restyle stock actions -> use `theme.consentActions`
|
|
29
|
+
|
|
30
|
+
> ⚠️ **Warning:**
|
|
31
|
+
> Headless mode is not the first answer for pure theming. If you are still trying to debug why a banner footer color did not change, stay in the styling system and verify the token-to-component mapping before you rebuild the UI.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Iframe Blocking
|
|
3
|
+
description: Reference page for iframe blocking.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
Embedded iframes from third parties (YouTube, Google Maps, social media widgets) can set cookies and track users without their consent. c15t provides two approaches to gate iframes behind consent:
|
|
7
|
+
|
|
8
|
+
1. **`<Frame>` component** - A React component that conditionally renders children based on consent
|
|
9
|
+
2. **HTML `data-category` attribute** - For raw `<iframe>` elements outside of React
|
|
10
|
+
|
|
11
|
+
### Custom Placeholder
|
|
12
|
+
|
|
13
|
+
Replace the default placeholder with your own UI:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Frame
|
|
17
|
+
category="marketing"
|
|
18
|
+
placeholder={
|
|
19
|
+
<div className="flex items-center justify-center h-64 bg-gray-100 rounded">
|
|
20
|
+
<p>Enable marketing cookies to watch this video.</p>
|
|
21
|
+
</div>
|
|
22
|
+
}
|
|
23
|
+
>
|
|
24
|
+
<iframe src="https://www.youtube.com/embed/..." />
|
|
25
|
+
</Frame>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Compound Components
|
|
29
|
+
|
|
30
|
+
Build custom placeholder layouts using compound components:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<Frame.Root category="marketing">
|
|
34
|
+
<Frame.Title category="marketing" />
|
|
35
|
+
<Frame.Button category="marketing" />
|
|
36
|
+
</Frame.Root>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## HTML Attribute Approach
|
|
40
|
+
|
|
41
|
+
For iframes outside of React (e.g., CMS content, server-rendered HTML), add `data-category` and use `data-src` instead of `src`:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<iframe
|
|
45
|
+
data-src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
46
|
+
data-category="marketing"
|
|
47
|
+
width="560"
|
|
48
|
+
height="315"
|
|
49
|
+
></iframe>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
When consent for the specified category is granted, c15t automatically swaps `data-src` to `src`, loading the iframe. When consent is revoked, `src` is moved back to `data-src`.
|
|
53
|
+
|
|
54
|
+
### Dynamic Iframes
|
|
55
|
+
|
|
56
|
+
c15t uses a `MutationObserver` to watch for dynamically added iframes. Any iframe with `data-category` added to the DOM after initialization is automatically processed.
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
|Property|Value|
|
|
61
|
+
|:--|:--|
|
|
62
|
+
|Type Name|\`FrameProps\`|
|
|
63
|
+
|Source Path|\`./packages/react/src/components/frame/types.ts\`|
|
|
64
|
+
|
|
65
|
+
\*ExtractedTypeTable: Could not extract "FrameProps" from "./packages/react/src/components/frame/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Internationalization
|
|
3
|
+
description: Reference page for internationalization.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
c15t ships with built-in translations for 30+ languages via the `@c15t/translations` package. Language detection happens automatically based on the browser's language preference, and you can override or extend translations for any language.
|
|
7
|
+
|
|
8
|
+
In c15t v2, the preferred config shape is `i18n` with `locale`, `detectBrowserLanguage`, and `messages`.
|
|
9
|
+
|
|
10
|
+
There are two ways c15t can load translations: client-side or server-side.
|
|
11
|
+
|
|
12
|
+
|Server-side|Client-side|
|
|
13
|
+
|--|--|
|
|
14
|
+
|The best way to reduce bundle size and improve performance. We can detect the user's language based on the browser's language settings, allowing for the most accurate translations. By default, when using a [inth.com](https://inth.com) hosted instance, [these languages](https://github.com/c15t/c15t/tree/main/packages/translations/src/translations) are supported.|Bundled with the application allowing for multiple languages to be supported without the need for a backend. The more translations you have, the larger the bundle size will be, which may impact the performance of your application.|
|
|
15
|
+
|
|
16
|
+
## Translation Package Imports
|
|
17
|
+
|
|
18
|
+
Use the import path that matches your use case:
|
|
19
|
+
|
|
20
|
+
* `@c15t/translations`: types, utilities, and `enTranslations` only (smallest client bundle)
|
|
21
|
+
* `@c15t/translations/en`: English-only translation object
|
|
22
|
+
* `@c15t/translations/all`: full `baseTranslations` map for all bundled locales
|
|
23
|
+
|
|
24
|
+
If you bundle translations client-side and need multiple languages, import from `@c15t/translations/all` explicitly:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { baseTranslations } from '@c15t/translations/all';
|
|
28
|
+
|
|
29
|
+
const translations = {
|
|
30
|
+
en: baseTranslations.en,
|
|
31
|
+
de: baseTranslations.de,
|
|
32
|
+
fr: baseTranslations.fr,
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Translation Sections
|
|
37
|
+
|
|
38
|
+
The `Translations` object is organized into sections:
|
|
39
|
+
|
|
40
|
+
|Section|Controls|
|
|
41
|
+
|--|--|
|
|
42
|
+
|`common`|Shared button labels: acceptAll, rejectAll, customize, save|
|
|
43
|
+
|`cookieBanner`|Banner title and description|
|
|
44
|
+
|`consentManagerDialog`|Dialog title and description|
|
|
45
|
+
|`consentTypes`|Per-category title and description (keyed by AllConsentNames)|
|
|
46
|
+
|`frame`|Frame placeholder title, button, loading, and error text (title and button support `{category}`)|
|
|
47
|
+
|`legalLinks`|Privacy policy, cookie policy, terms of service link text|
|
|
48
|
+
|`iab`|IAB TCF banner, preference center, vendor list translations|
|
|
49
|
+
|
|
50
|
+
## Automatic Language Detection
|
|
51
|
+
|
|
52
|
+
By default, c15t detects the browser's language (`navigator.language`) and selects the closest matching translation. If custom `messages` are configured, fallback stays within your configured languages before using `locale` (or `'en'`) as the preferred fallback language.
|
|
53
|
+
|
|
54
|
+
When backend policy packs use `i18n.messageProfile`, the active language pool comes only from that resolved profile.
|
|
55
|
+
|
|
56
|
+
Example: if your Europe profile defines `en`, `fr`, and `de`, and your default profile defines `en`, `es`, and `pt`, a Europe visitor can resolve to `en`, `fr`, or `de`, but not `es`, `pt`, or `zh`.
|
|
57
|
+
|
|
58
|
+
Use profile-local `fallbackLanguage` inside backend `i18n.messages` to choose which configured language a policy profile should fall back to when the browser asks for an unsupported locale.
|
|
59
|
+
|
|
60
|
+
Disable auto-detection to always use `locale`:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
i18n: {
|
|
64
|
+
locale: 'de',
|
|
65
|
+
detectBrowserLanguage: false,
|
|
66
|
+
messages: { ... },
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If you need a policy to always use one specific language regardless of browser preference, set `policy.i18n.language` on the backend policy pack.
|
|
71
|
+
|
|
72
|
+
## Custom Consent Type Labels
|
|
73
|
+
|
|
74
|
+
Override the title and description for individual consent categories. Frame
|
|
75
|
+
placeholders use that title anywhere their message contains `{category}`, so
|
|
76
|
+
the title and action stay consistent:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
i18n: {
|
|
80
|
+
messages: {
|
|
81
|
+
en: {
|
|
82
|
+
consentTypes: {
|
|
83
|
+
measurement: {
|
|
84
|
+
title: 'Analytics & Performance',
|
|
85
|
+
description: 'Help us understand how visitors interact with our site.',
|
|
86
|
+
},
|
|
87
|
+
marketing: {
|
|
88
|
+
title: 'Advertising',
|
|
89
|
+
description: 'Used to deliver relevant ads and measure campaign effectiveness.',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
frame: {
|
|
93
|
+
title: 'Allow {category} consent to view this content.',
|
|
94
|
+
actionButton: 'Enable {category} consent',
|
|
95
|
+
loading: 'Loading content…',
|
|
96
|
+
error: 'This content could not be loaded.',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Legacy `translations` Compatibility
|
|
104
|
+
|
|
105
|
+
The legacy syntax is still supported in 2.0 for RC compatibility:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Legacy (still supported)
|
|
109
|
+
translations: {
|
|
110
|
+
defaultLanguage: 'en',
|
|
111
|
+
disableAutoLanguageSwitch: true,
|
|
112
|
+
translations: { ... },
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Preferred in v2
|
|
116
|
+
i18n: {
|
|
117
|
+
locale: 'en',
|
|
118
|
+
detectBrowserLanguage: false,
|
|
119
|
+
messages: { ... },
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If both are provided, `i18n` takes precedence.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Network Blocker
|
|
3
|
+
description: Reference page for network blocker.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
The network blocker intercepts outgoing `fetch` and `XMLHttpRequest` calls and blocks them based on consent state and domain rules. This catches tracking requests that happen outside of script loading - for example, beacon calls, API requests to analytics endpoints, or pixel fires from already-loaded scripts.
|
|
7
|
+
|
|
8
|
+
## Rule Matching
|
|
9
|
+
|
|
10
|
+
Rules match requests using three criteria:
|
|
11
|
+
|
|
12
|
+
### Domain matching
|
|
13
|
+
|
|
14
|
+
The `domain` field matches the request hostname. Subdomains are automatically included - a rule for `google-analytics.com` also matches `www.google-analytics.com` and `stats.google-analytics.com`.
|
|
15
|
+
|
|
16
|
+
### Path matching
|
|
17
|
+
|
|
18
|
+
The optional `pathIncludes` field requires the request URL path to contain the specified substring. This lets you target specific endpoints without blocking the entire domain.
|
|
19
|
+
|
|
20
|
+
### Method matching
|
|
21
|
+
|
|
22
|
+
The optional `methods` array restricts the rule to specific HTTP methods. If omitted, the rule applies to all methods.
|
|
23
|
+
|
|
24
|
+
## Consent Conditions
|
|
25
|
+
|
|
26
|
+
Like the script loader, `category` accepts a `HasCondition`:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// Simple
|
|
30
|
+
{ category: 'measurement' }
|
|
31
|
+
|
|
32
|
+
// Must have both
|
|
33
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
34
|
+
|
|
35
|
+
// Must have either
|
|
36
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Monitoring Blocked Requests
|
|
40
|
+
|
|
41
|
+
### Console logging
|
|
42
|
+
|
|
43
|
+
Blocked requests are logged to the console by default. Disable with `logBlockedRequests: false`.
|
|
44
|
+
|
|
45
|
+
### Callback
|
|
46
|
+
|
|
47
|
+
Use `onRequestBlocked` to handle blocked requests programmatically:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
networkBlocker: {
|
|
51
|
+
rules: [...],
|
|
52
|
+
onRequestBlocked: ({ method, url, rule }) => {
|
|
53
|
+
console.log(`Blocked ${method} ${url} (rule: ${rule?.id})`);
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
|Property|Value|
|
|
61
|
+
|:--|:--|
|
|
62
|
+
|Type Name|\`NetworkBlockerRule\`|
|
|
63
|
+
|Source Path|\`./packages/core/src/libs/network-blocker/types.ts\`|
|
|
64
|
+
|
|
65
|
+
\*ExtractedTypeTable: Could not extract "NetworkBlockerRule" from "./packages/core/src/libs/network-blocker/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
|
|
66
|
+
|
|
67
|
+
|Property|Value|
|
|
68
|
+
|:--|:--|
|
|
69
|
+
|Type Name|\`NetworkBlockerConfig\`|
|
|
70
|
+
|Source Path|\`./packages/core/src/libs/network-blocker/types.ts\`|
|
|
71
|
+
|
|
72
|
+
\*ExtractedTypeTable: Could not extract "NetworkBlockerConfig" from "./packages/core/src/libs/network-blocker/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Optimization
|
|
3
|
+
description: Reference page for optimization.
|
|
4
|
+
group: reference
|
|
5
|
+
---
|
|
6
|
+
Use this guide when you care about banner visibility speed, route static-ness, and reducing backend round-trip cost.
|
|
7
|
+
|
|
8
|
+
Why this helps:
|
|
9
|
+
|
|
10
|
+
* Same-origin requests avoid extra DNS/TLS setup in many deployments
|
|
11
|
+
* Ad blockers are less likely to block your init endpoint
|
|
12
|
+
* You can change backend infrastructure without touching client code
|
|
13
|
+
|
|
14
|
+
In production benchmarks with a same-origin rewrite, prefetching strategies show measurable improvement over client-only init:
|
|
15
|
+
|
|
16
|
+
|Strategy|Scripts loaded|Data request starts|Banner visible|
|
|
17
|
+
|--|--|--|--|
|
|
18
|
+
|Client-only (no prefetch)|baseline|baseline|baseline|
|
|
19
|
+
|Browser prefetch|\~1.3x faster|\~2.6x earlier|\~1.25x faster|
|
|
20
|
+
|Server prefetch|\~2x faster|before page loads|\~1.9x faster|
|
|
21
|
+
|
|
22
|
+
## Keep The Provider Mounted Across Navigation
|
|
23
|
+
|
|
24
|
+
Mount the consent provider at the app root so route transitions do not remount it.
|
|
25
|
+
|
|
26
|
+
Why this helps:
|
|
27
|
+
|
|
28
|
+
* Avoids re-running init work on client-side navigation
|
|
29
|
+
* Prevents extra callback churn from remount cycles
|
|
30
|
+
* Keeps banner/dialog state stable between route transitions
|
|
31
|
+
|
|
32
|
+
## Animation Performance
|
|
33
|
+
|
|
34
|
+
The default motion tokens are tuned for speed-first product UI:
|
|
35
|
+
|
|
36
|
+
|Token|Duration|Used for|
|
|
37
|
+
|--|--|--|
|
|
38
|
+
|`fast`|80ms|Banner slide + overlay, card scale, button hover, widget entry/exit|
|
|
39
|
+
|`normal`|150ms|Accordion, switch toggle|
|
|
40
|
+
|`slow`|200ms|Dialog trigger snap, tab indicator|
|
|
41
|
+
|
|
42
|
+
These defaults follow the principle that product UI should be fast and purposeful — animations exist for spatial continuity, not decoration. In benchmarks, animation duration contributes a constant floor to "data fetched → banner visible" timing. The default tokens sit at the lower end of standard UI ranges (80-200ms) to minimize that floor.
|
|
43
|
+
|
|
44
|
+
If you must use a cross-origin backend URL, add preconnect so the browser starts DNS/TLS early:
|