@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,8 +1,8 @@
1
1
  ---
2
- title: Policy presets
3
- description: Understand how policy rules affect React prompts, permissions and
4
- persistent privacy controls.
5
- group: frameworks
2
+ title: Policies
3
+ description: How policy models, prompts and scope decide what c15t asks
4
+ visitors, where to change the rules, and why a banner may not appear.
5
+ group: concepts
6
6
  ---
7
7
 
8
8
  ## What the policy controls
@@ -29,12 +29,12 @@ must do the same.
29
29
  Use `effectivePermissions` to gate a script or embed. A `true` value under an
30
30
  opt-out rule does not mean the visitor clicked Accept. Read `explicitChoice`
31
31
  when you need the visitor's recorded decision. See
32
- [consent state](../../../guides/consent-state.md) for these distinctions.
32
+ [how consent works](./how-consent-works.md#a-permission-is-not-a-recorded-choice) for these distinctions.
33
33
 
34
34
  ## Where to change the rules
35
35
 
36
36
  For Inth, configure policy rules on your hosted project. The app receives those
37
- rules through the configured [data-fetching path](../../../guides/data-fetching.md). Importing
37
+ rules through the configured [data-fetching path](./data-fetching.md). Importing
38
38
  `recommendedPolicyRules()` or changing browser styling does not override the
39
39
  hosted policy.
40
40
 
@@ -42,7 +42,7 @@ For a backend you operate, use the
42
42
  [policy packs guide](https://c15t.com/docs/self-host/guides/policy-packs). It covers
43
43
  `manifest.policyRules`, preset selection and custom matchers. Browser-only
44
44
  setups own their rules locally; see
45
- [deployment modes](../../../guides/deployment-modes.md).
45
+ [choose your setup](./choose-your-setup.md).
46
46
 
47
47
  Presets are starting configurations. Select them for your actual processing,
48
48
  and review their assumptions before allowing optional categories by default.
@@ -69,55 +69,3 @@ default for other known locations. An unknown country uses its strict opt-in
69
69
  fallback. A US country with a missing state gets the US opt-out fallback.
70
70
  These are the local pack's defaults, not a promise about your Inth project.
71
71
  Browser-only mode does not discover a visitor's country from their IP address.
72
-
73
- ## Inspect the active policy
74
-
75
- Place this diagnostic component inside your existing consent
76
- provider. It reads state without recording a choice.
77
-
78
- ```tsx title="src/consent-policy-debug.tsx"
79
- import { useSnapshot } from 'c15t/react';
80
-
81
- export function ConsentPolicyDebug() {
82
- const snapshot = useSnapshot();
83
- const resolution = snapshot.resolution;
84
-
85
- return (
86
- <pre>
87
- {JSON.stringify(
88
- {
89
- status: resolution.status,
90
- policy: resolution.status === 'matched' ? resolution.policy.id : null,
91
- prompt: snapshot.promptRequirement,
92
- permissions: snapshot.effectivePermissions,
93
- location: snapshot.location,
94
- privacySignals: snapshot.privacySignals,
95
- },
96
- null,
97
- 2,
98
- )}
99
- </pre>
100
- );
101
- }
102
- ```
103
-
104
- The diagnostic subscribes to consent changes. Remove it after verification.
105
- For a production feature gate, use `useConsent('marketing')` or the category
106
- your feature needs instead of subscribing to the whole snapshot.
107
-
108
- ## Change appearance without changing policy
109
-
110
- Keep one banner and one dialog in your existing
111
- provider. Use
112
- [styling](../styling/overview.md) to change their appearance,
113
- or [headless UI](../headless.md) for custom markup.
114
- Presentation settings change layout and emphasis; policy rules decide which
115
- actions and privacy rights the app must expose.
116
-
117
- ## Verify policy behavior
118
-
119
- Use a fresh browser profile to check the intended location, then repeat with a
120
- saved rejection and GPC enabled. Confirm that the expected prompt and
121
- preferences controls appear, and that denied scripts and embeds make no
122
- requests. Test missing location inputs too. Follow
123
- [verify consent](../../../guides/verify-consent.md) for the full flow.
@@ -0,0 +1,202 @@
1
+ ---
2
+ title: Class names and CSS-in-JS
3
+ description: Style c15t's component parts with CSS Modules, vanilla-extract,
4
+ StyleX, Emotion or plain class names, and see which approach works in each
5
+ framework.
6
+ group: customization
7
+ ---
8
+
9
+ ## Pass a class to a part
10
+
11
+ CSS Modules, vanilla-extract, StyleX, Emotion and Tailwind all end up as a class
12
+ on an element and a rule in a stylesheet. Give c15t the class through your
13
+ framework's part API, and the rule wins over c15t's own rule for that part:
14
+ c15t's rules sit in the `components` cascade layer, and an unlayered rule from
15
+ your stylesheet outranks any layered one.
16
+
17
+ Each example below adds a 3px colored border and 4px corners to the banner card.
18
+ The React examples run as Storybook stories in CI, which check that each class
19
+ lands on the card and that the computed border comes from the class, not from
20
+ c15t's own card rule. The script tag examples run in the example app's browser
21
+ tests.
22
+
23
+ | Framework | Part API | Class key | Inline `style` | Where the class's CSS must load |
24
+ | ------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------- | -------------- | ------------------------------------------------------------------------------------ |
25
+ | Next.js, TanStack Start, React | `components.<component>.<part>`, or `theme.slots` | `className` | Yes | Anywhere on the page |
26
+ | Nuxt, Vue | `components.<component>.<part>`, or `theme.slots` | `class` (`className` in `theme.slots`) | Yes | Anywhere on the page |
27
+ | Svelte, SvelteKit | `theme.slots`, or `class` on a component | A string, or `className` in a slot object | Yes | A global stylesheet, or `:global()` |
28
+ | Astro | `theme.slots` in the integration options, or `class` on `ConsentBanner` | A string, or `className` in a slot object | Yes | A global stylesheet |
29
+ | HTML, JavaScript | `ui.theme.slots` | A string, or `className` in a slot object | Yes | Inside the shadow root through `ui.stylesheetURLs`, or anywhere with `shadow: false` |
30
+
31
+ [Component parts](./slots.md) lists the parts and their keys in
32
+ each framework.
33
+
34
+ ## CSS Modules
35
+
36
+ Import the module and pass its class. This works wherever your bundler compiles
37
+ CSS Modules:
38
+
39
+ ```css title="src/banner.module.css"
40
+ .card {
41
+ border: 3px solid rgb(219 39 119);
42
+ border-radius: 4px;
43
+ }
44
+ ```
45
+
46
+ ```ts title="src/consent-components.ts"
47
+ import type { ConsentProviderOptions } from 'c15t/react';
48
+
49
+ import styles from './banner.module.css';
50
+
51
+ /** Pass as `components` in your ConsentProvider options. */
52
+ export const components: ConsentProviderOptions['components'] = {
53
+ banner: { card: { className: styles.card } },
54
+ };
55
+ ```
56
+
57
+ Pass `components` in your `ConsentProvider` options, or in `options` on
58
+ `ConsentRoot` in Next.js and TanStack Start.
59
+
60
+ ## vanilla-extract
61
+
62
+ vanilla-extract compiles `style()` calls in `.css.ts` files to static CSS at
63
+ build time. Add its plugin for your bundler, such as
64
+ `@vanilla-extract/vite-plugin`, then pass the class:
65
+
66
+ ```ts title="src/consent-components.css.ts"
67
+ import { style } from '@vanilla-extract/css';
68
+ import type { ConsentProviderOptions } from 'c15t/react';
69
+
70
+ const card = style({ border: '3px solid rgb(37 99 235)', borderRadius: 4 });
71
+
72
+ /** Pass as `components` in your ConsentProvider options. */
73
+ export const components: ConsentProviderOptions['components'] = {
74
+ banner: { card: { className: card } },
75
+ };
76
+ ```
77
+
78
+ ## StyleX
79
+
80
+ `stylex.props()` returns `className` and, for dynamic values, `style`. A React
81
+ part accepts both, so spread the result into the part. Add the StyleX compiler
82
+ plugin for your bundler, such as `@stylexjs/unplugin`:
83
+
84
+ ```ts title="src/consent-components.ts"
85
+ import * as stylex from '@stylexjs/stylex';
86
+ import type { ConsentProviderOptions } from 'c15t/react';
87
+
88
+ const styles = stylex.create({
89
+ card: {
90
+ borderColor: 'rgb(22 163 74)',
91
+ borderRadius: 4,
92
+ borderStyle: 'solid',
93
+ borderWidth: 3,
94
+ },
95
+ });
96
+
97
+ /**
98
+ * Pass as `components` in your ConsentProvider options. `stylex.props()`
99
+ * returns `className` and, for dynamic styles, `style`; the slot takes both.
100
+ */
101
+ export const components: ConsentProviderOptions['components'] = {
102
+ banner: { card: stylex.props(styles.card) },
103
+ };
104
+ ```
105
+
106
+ StyleX writes atomic classes into an unlayered stylesheet, so they outrank
107
+ c15t's layered rules without extra specificity.
108
+
109
+ ## Emotion
110
+
111
+ `css()` from `@emotion/css` returns a class name and inserts its rule into a
112
+ `<style>` element in the document `<head>` when your code runs:
113
+
114
+ ```ts title="src/consent-components.ts"
115
+ import { css } from '@emotion/css';
116
+ import type { ConsentProviderOptions } from 'c15t/react';
117
+
118
+ const card = css({ border: '3px solid rgb(234 88 12)', borderRadius: 4 });
119
+
120
+ /** Pass as `components` in your ConsentProvider options. */
121
+ export const components: ConsentProviderOptions['components'] = {
122
+ banner: { card: { className: card } },
123
+ };
124
+ ```
125
+
126
+ Emotion inserts its rules into the page, not into a shadow root. With the HTML
127
+ script tag or `@c15t/browser`, set `ui: { shadow: false }` so the UI renders
128
+ in the page where those rules reach it. `ui.stylesheetURLs` cannot carry them,
129
+ because they have no URL.
130
+
131
+ ## Use other frameworks' part APIs
132
+
133
+ Vue and Nuxt bind a part's object to the element with `v-bind`, so write
134
+ `class` rather than `className`. A class string from CSS Modules,
135
+ vanilla-extract or Emotion works the same way. To use StyleX, map its
136
+ `className` to `class`.
137
+
138
+ Svelte scopes component styles, so a class defined in a component's `<style>`
139
+ block does not reach a c15t part. Define it in a global stylesheet or with
140
+ `:global(.your-class)`. `theme.slots` accepts a string or
141
+ `{ className, style }`, and `class` on `ConsentBanner` goes on the banner root.
142
+
143
+ Astro serializes its integration options, so `theme.slots` in
144
+ `astro.config.mjs` takes plain strings and objects. Use class names from a
145
+ global stylesheet or Tailwind there. Build-time tools that export class names
146
+ from a module, such as CSS Modules, cannot reach `astro.config.mjs`.
147
+
148
+ ## Style the script tag's shadow root
149
+
150
+ The script tag and `init()` from `@c15t/browser` render into a shadow root. A
151
+ class from your page's stylesheet reaches a part only if the stylesheet reaches
152
+ the shadow root. Pick one:
153
+
154
+ * **`ui.stylesheetURLs`.** c15t links each URL inside the shadow root, after
155
+ its own stylesheet. Link the stylesheet that holds your classes, such as your
156
+ CSS Modules or Tailwind build.
157
+ * **`::part()`.** Every part with a slot key carries it in a `part` attribute.
158
+ Page CSS can style it with `[data-c15t-ui]::part(consentBannerCard)`,
159
+ without any class.
160
+ * **`ui.css`.** A string of CSS that c15t adds inside the shadow root.
161
+ * **`shadow: false`.** c15t renders into the page and every page stylesheet
162
+ applies, including Emotion's.
163
+
164
+ This example links a Tailwind build into the shadow root and puts utilities on
165
+ two parts:
166
+
167
+ ```html title="index.html"
168
+ <link
169
+ rel="stylesheet"
170
+ href="/tailwind.css"
171
+ />
172
+ <script>
173
+ window.c15t = window.c15t || [];
174
+ c15t.push([
175
+ 'config',
176
+ {
177
+ ui: {
178
+ // The banner renders in a shadow root, where the page's
179
+ // stylesheets do not reach. Link your Tailwind build into it
180
+ // too, and keep the page's own link: Tailwind 4 registers
181
+ // variables with @property, which only works in the page.
182
+ stylesheetURLs: ['/tailwind.css'],
183
+ theme: {
184
+ slots: {
185
+ consentBannerCard: 'rounded-none border-4 border-sky-600',
186
+ consentBannerTitle: 'uppercase tracking-wide',
187
+ },
188
+ },
189
+ },
190
+ },
191
+ ]);
192
+ </script>
193
+ ```
194
+
195
+ ## Check the result
196
+
197
+ 1. Open the page in a private window so the banner shows.
198
+ 2. Select the banner card in the Elements panel. It carries your class.
199
+ 3. In the Styles panel, your rule applies and c15t's rule for the same
200
+ property is struck out. If your class is on the element but its rule is
201
+ missing, the stylesheet does not reach the part. Check the "Where the class's CSS
202
+ must load" column in [Pass a class to a part](#pass-a-class-to-a-part).
@@ -0,0 +1,157 @@
1
+ ---
2
+ title: Dark mode
3
+ description: Switch c15t's banner and dialog to dark colors with colorScheme,
4
+ set your own dark tokens, follow your site's theme switch, and paint dark on
5
+ the first frame in every framework.
6
+ group: customization
7
+ ---
8
+
9
+ ## How c15t turns dark
10
+
11
+ c15t's stylesheet switches every `--c15t-*` color token to its dark value when
12
+ `<html>` has a `dark` or a `c15t-dark` class. The `colorScheme` option decides
13
+ whether c15t sets `c15t-dark` itself:
14
+
15
+ | `colorScheme` | What c15t does with `c15t-dark` |
16
+ | ------------- | ------------------------------------------------------------------------ |
17
+ | `'light'` | Removes it once |
18
+ | `'dark'` | Adds it once |
19
+ | `'system'` | Follows `prefers-color-scheme`, including changes while the page is open |
20
+ | Unset | Copies your `dark` class into `c15t-dark` and follows it as it changes |
21
+ | `null` | Leaves it alone. Your site sets it |
22
+
23
+ Astro also accepts `'none'`, which means the same as `null`.
24
+
25
+ `'light'` only removes `c15t-dark`. If your site puts `dark` on `<html>`, the
26
+ stylesheet still switches the tokens to dark. Pick one class convention for
27
+ your site and let c15t follow it.
28
+
29
+ ## Defaults in each framework
30
+
31
+ | Framework | Where to set it | Default |
32
+ | ----------------------- | --------------------------------------------------------------------------------- | -------------------- |
33
+ | Next.js, TanStack Start | `options.colorScheme` on `ConsentRoot`, and `colorScheme` on `ConsentTheme` | Unset: copies `dark` |
34
+ | React | `colorScheme` in the `ConsentProvider` options, and on `ConsentTheme` | Unset: copies `dark` |
35
+ | Nuxt | `colorScheme` under the `c15t` key in `nuxt.config.ts`, or in `app/app.config.ts` | Unset: copies `dark` |
36
+ | Vue | `colorScheme` in the `c15tVue` options | Unset: copies `dark` |
37
+ | Astro | `colorScheme` in the `c15t()` integration options | `'system'` |
38
+ | Svelte, SvelteKit | `colorScheme` on `ConsentManagerProvider` | Unset: copies `dark` |
39
+ | HTML | `data-color-scheme` on the script tag: `light`, `dark`, `system` or `none` | `system` |
40
+ | JavaScript | `ui.colorScheme` in `init()` | `'system'` |
41
+
42
+ React, Vue and Svelte apps usually already have a theme switch that toggles a
43
+ `dark` class, such as next-themes. Copying that class keeps c15t in step with
44
+ the site without extra code.
45
+
46
+ Astro and the script tag default to `'system'` because neither can rely on a
47
+ site convention. Astro paints the banner from server HTML before any site
48
+ script runs, and Astro sites share no `dark` class convention, so following the
49
+ operating system is the one choice its inline script can make correctly. A plain
50
+ HTML page has no convention either. A site that has one opts in with `null`.
51
+
52
+ ### Set colorScheme null in Nuxt
53
+
54
+ In Nuxt, set `colorScheme: null` under the `c15t` key in `nuxt.config.ts` or
55
+ in `app/app.config.ts`. Nuxt drops a `null` in inline module options, such as
56
+ `modules: [['@c15t/vue', { colorScheme: null }]]`, before the module reads it,
57
+ so c15t would copy your `dark` class as if `colorScheme` were unset.
58
+
59
+ ## Follow your site's theme switch
60
+
61
+ In React, Next.js, TanStack Start, Vue, Nuxt, Svelte and SvelteKit, leave
62
+ `colorScheme` unset and toggle `dark` on `<html>`. c15t watches the class and switches with
63
+ it. Include the class in the server HTML when your site renders dark, so the
64
+ first paint matches.
65
+
66
+ In Astro, set `colorScheme: 'none'` and toggle `c15t-dark` together with your
67
+ own class. A `ClientRouter` navigation replaces the attributes of `<html>`, so
68
+ set the class again on `astro:after-swap` if your theme script does not.
69
+
70
+ With the script tag, add `data-color-scheme="none"`. With `init()`, pass
71
+ `ui: { colorScheme: null }`. The UI is then dark while `<html>` has `dark` or
72
+ `c15t-dark`, and follows the class as it changes. The UI renders in a shadow
73
+ root that the page's class cannot reach, so c15t copies the class onto the
74
+ shadow host.
75
+
76
+ If your site sets `c15t-dark` itself in React, Next.js, TanStack Start, Vue,
77
+ Nuxt, Svelte or SvelteKit, pass `colorScheme: null` so c15t leaves it alone.
78
+
79
+ ## Set your own dark colors
80
+
81
+ Theme tokens take a `dark` object with the same color keys as `colors`. Every
82
+ color you set in `colors` also applies in dark mode, unless `dark` sets it too,
83
+ so give a dark value for each brand color:
84
+
85
+ ```ts title="consent-theme.ts"
86
+ export const theme = {
87
+ colors: { primary: '#2f6f4e', primaryHover: '#24563c', surface: '#fbf8f3' },
88
+ dark: { primary: '#7fd1a8', primaryHover: '#9fdcbd', surface: '#1b1f1d' },
89
+ };
90
+ ```
91
+
92
+ Where the theme goes depends on the framework:
93
+
94
+ * **React, Next.js, TanStack Start.** Pass it to `ConsentTheme`. To write the
95
+ CSS outside a component, call `generateThemeCSS(theme, colorScheme)` from
96
+ `c15t/react/utils`, or `@c15t/react/utils` if you installed the scoped
97
+ package. It writes the same CSS as `ConsentTheme`.
98
+ * **Vue, Nuxt.** Pass it as `theme` in the plugin or module options. It goes
99
+ into the `<style id="c15t-css-vars">` element with `tokens`.
100
+ * **Astro.** Pass it as `theme` in the integration options.
101
+ * **SvelteKit, Svelte.** Pass it to `generateThemeCSS(theme, colorScheme)` from
102
+ `@c15t/ui/theme` on the server.
103
+ * **HTML, JavaScript.** Pass it as `ui.theme`.
104
+
105
+ This Nuxt example follows the system setting and uses its own dark primary
106
+ color. Every other dark token keeps c15t's default:
107
+
108
+ ```ts title="nuxt.config.ts (c15t options)"
109
+ // Follow the visitor's system setting, with a dark primary of our own.
110
+ colorScheme: 'system',
111
+ theme: { dark: { primary: '#7fd1a8' } },
112
+ ```
113
+
114
+ To set dark values in plain CSS instead, write them for both classes:
115
+
116
+ ```css
117
+ :root.dark,
118
+ :root.c15t-dark {
119
+ --c15t-primary: #7fd1a8;
120
+ --c15t-surface: #1b1f1d;
121
+ }
122
+ ```
123
+
124
+ A generated theme, such as the output of `ConsentTheme` or
125
+ `generateThemeCSS`, writes its dark values on `:root:root.dark` and
126
+ `:root:root.c15t-dark`. Those selectors outrank the rule above. If you use both,
127
+ put dark values in the theme, or repeat `:root` in your own selectors.
128
+
129
+ ## Paint dark on the first frame
130
+
131
+ A banner that renders light and then turns dark flashes. How to avoid that
132
+ depends on where the banner first renders:
133
+
134
+ | Framework | What to do |
135
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | Next.js, TanStack Start | Render `<ConsentTheme theme={theme} colorScheme="system" />` on the server, with the same value as `options.colorScheme`. `'dark'` writes dark tokens as the default and `'system'` adds a `prefers-color-scheme` media query, so the server HTML is dark before hydration. With `colorScheme` unset, put your `dark` class in the server HTML. |
137
+ | React | Render `<ConsentTheme colorScheme="system" />` next to the provider, with the same value as the provider's `colorScheme`. Its CSS makes the tokens dark from the first frame, before the provider sets the class. In a server-rendered React app, render it on the server. |
138
+ | Nuxt | For `'dark'` and `'system'`, the module adds an inline script to `<head>` that sets `c15t-dark` before the server-rendered banner paints. It carries the module's `nonce`. With `colorScheme` unset, put your `dark` class in the server HTML. |
139
+ | Vue | The plugin applies `colorScheme` and writes the tokens when you install it, before the first render. |
140
+ | Astro | `ConsentScript` in `<head>` sets the class from an inline script before the banner paints, and c15t sets it again after each `ClientRouter` navigation. Keep `ConsentScript` in your layout's `<head>`. |
141
+ | Svelte | The provider sets the class as it mounts. With `colorScheme` unset, the stylesheet reads your `dark` class directly, so the tokens are dark from the first frame. |
142
+ | SvelteKit | Pass the scheme to `generateThemeCSS(theme, 'system')` in your server load, so the CSS in `<svelte:head>` already has the media query. With `colorScheme` unset, put your `dark` class in the server HTML. |
143
+ | HTML, JavaScript | c15t applies the scheme when it mounts the UI, before any surface renders. |
144
+
145
+ [Theme tokens](./tokens.md) lists every color token, and your
146
+ framework's customize page shows where its theme goes.
147
+
148
+ ## Check the result
149
+
150
+ 1. Open the page in a private window with your operating system in dark mode,
151
+ or add your `dark` class to `<html>`.
152
+ 2. The banner is dark on its first frame. Reload with the Network panel
153
+ throttled to see the first paint.
154
+ 3. Open the preference dialog. It uses the same dark tokens as the banner.
155
+ 4. Switch the scheme while the page is open. With `'system'` or an unset
156
+ `colorScheme`, the banner and dialog follow without a reload.
157
+ 5. Check the contrast of the primary button text in both schemes.
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Motion and animation
3
+ description: Change how fast c15t's banner and dialog animate with duration and
4
+ easing tokens, turn animations off per surface with disableAnimation, and
5
+ check reduced-motion behavior in each framework.
6
+ group: customization
7
+ ---
8
+
9
+ ## Change the speed with motion tokens
10
+
11
+ c15t animates its surfaces with three durations and four easing curves. Set
12
+ them in the theme's `motion` object, or as CSS variables:
13
+
14
+ | CSS variable | Theme key | Default | Used by |
15
+ | ------------------------ | ------------------------ | -------------------------------------- | ----------------------------------------------- |
16
+ | `--c15t-duration-fast` | `motion.duration.fast` | `80ms` | Banner, buttons, tabs |
17
+ | `--c15t-duration-normal` | `motion.duration.normal` | `150ms` | Preference dialog, switches, accordions |
18
+ | `--c15t-duration-slow` | `motion.duration.slow` | `200ms` | Legal links, floating trigger |
19
+ | `--c15t-easing` | `motion.easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | Banner fade, switches, accordions |
20
+ | `--c15t-easing-out` | `motion.easingOut` | `cubic-bezier(0.215, 0.61, 0.355, 1)` | Preference dialog, floating trigger hover |
21
+ | `--c15t-easing-in-out` | `motion.easingInOut` | `cubic-bezier(0.645, 0.045, 0.355, 1)` | Floating trigger snapping to a corner |
22
+ | `--c15t-easing-spring` | `motion.easingSpring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | The banner's slide in and the dialog's scale in |
23
+
24
+ This theme slows the dialog down and removes the banner's overshoot:
25
+
26
+ ```ts title="consent-theme.ts"
27
+ export const theme = {
28
+ motion: {
29
+ duration: { fast: '120ms', normal: '220ms' },
30
+ easingSpring: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
31
+ },
32
+ };
33
+ ```
34
+
35
+ The theme goes where your framework's other tokens go. See
36
+ [theme tokens](./tokens.md) and your framework's customize page.
37
+ In Vue and Nuxt, set `tokens` with the variable name without the leading
38
+ `--`, such as `'c15t-duration-normal': '220ms'`.
39
+
40
+ ## Turn animations off
41
+
42
+ `disableAnimation` removes the enter and exit transitions of the banner and
43
+ dialogs, and the hover and snap transitions of the floating
44
+ `ConsentDialogTrigger`. Set it once for every surface, then override it for one
45
+ surface where your framework allows:
46
+
47
+ | Framework | For every surface | For one surface |
48
+ | ----------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
49
+ | Next.js, TanStack Start | `options.disableAnimation` on `ConsentRoot` | `disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog` |
50
+ | React | `disableAnimation` in the `ConsentProvider` options | The same component props |
51
+ | Nuxt | `disableAnimation` in the `c15t` module options | `disableAnimation` prop on `consent-banner.vue`, `consent-manager.vue` and the IAB banner and dialog, when you render them yourself |
52
+ | Vue | `disableAnimation` in the `c15tVue` options | The same component props |
53
+ | Astro | `disableAnimation` in the `c15t()` integration options | `disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog` |
54
+ | Svelte, SvelteKit | `disableAnimation` on `ConsentManagerProvider` | `disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog` |
55
+ | HTML | `data-disable-animation` on the script tag, or `ui.disableAnimation` in `config` | `ui.banner.disableAnimation`, `ui.dialog.disableAnimation` |
56
+ | JavaScript | `ui.disableAnimation` in `init()` | `ui.banner.disableAnimation`, `ui.dialog.disableAnimation` |
57
+
58
+ A value on one surface wins over the value for every surface.
59
+
60
+ Vue's `ConsentRoot` renders the banner and dialog without props, so in Vue and
61
+ Nuxt a per-surface value only applies when you render the surface components
62
+ yourself.
63
+
64
+ ## Follow the visitor's reduced motion setting
65
+
66
+ c15t's stylesheet stops the banner, dialog, floating trigger, switches, tabs
67
+ and accordions from animating while the visitor asks for reduced motion. The
68
+ rules sit in a `prefers-reduced-motion: reduce` media query, so they apply in
69
+ every framework and follow the setting as it changes, with no option to set.
70
+
71
+ `disableAnimation: false` does not bring the animations back for these
72
+ visitors, because the stylesheet rule applies whatever the option says.
73
+
74
+ ## Animate your own rules on dialog state
75
+
76
+ The preference dialog marks its open state with `data-state`, `open` or
77
+ `closed`, so a rule can animate your own additions to it. Which element
78
+ carries the attribute depends on the framework:
79
+
80
+ | Framework | Elements with `data-state` |
81
+ | ------------------------------ | --------------------------------------------------------------------------------------------------- |
82
+ | HTML, JavaScript | Dialog overlay, positioner and content |
83
+ | Svelte, SvelteKit | Dialog backdrop, positioner and content |
84
+ | Vue, Nuxt | Dialog content and overlay. The dialog unmounts when it closes, so you only see `open` |
85
+ | React, Next.js, TanStack Start | Not on `ConsentDialog`. The `Dialog` primitive's trigger, overlay, content and close parts carry it |
86
+
87
+ Banners do not carry `data-state`. Read `data-prompt`, `data-variant` and the
88
+ other attributes in [component parts](./slots.md) instead.
89
+
90
+ ## Stop transitions while you switch themes
91
+
92
+ Every c15t stylesheet has a `c15t-no-transitions` class that sets
93
+ `transition` and `animation` to `none` on an element and its children. Add it
94
+ to `<html>` while your app swaps themes, so colors change in one frame. Force
95
+ a style and layout pass before you remove it. Otherwise the browser computes
96
+ the new theme only after the class is gone, and the change animates:
97
+
98
+ ```ts
99
+ const root = document.documentElement;
100
+ root.classList.add('c15t-no-transitions');
101
+ applyYourTheme();
102
+ // Reading layout applies the new theme while transitions are off.
103
+ root.getBoundingClientRect();
104
+ root.classList.remove('c15t-no-transitions');
105
+ ```
106
+
107
+ This assumes `applyYourTheme` changes classes or custom properties
108
+ synchronously. If your framework applies the theme in a later render, run the
109
+ last two lines after that render commits.
110
+
111
+ ## Check the result
112
+
113
+ 1. In DevTools, open the Rendering panel and emulate
114
+ `prefers-reduced-motion: reduce`. Reload with site data cleared.
115
+ 2. The banner appears without sliding in.
116
+ 3. Open the preference dialog and toggle a switch. The switch changes without
117
+ animating.
118
+ 4. Turn the emulation off, set a slower `motion.duration.normal`, and open the
119
+ dialog again. It fades in at the new speed.