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