@c15t/scripts 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 (300) hide show
  1. package/AGENTS.md +129 -63
  2. package/README.md +8 -29
  3. package/SKILL.md +33 -0
  4. package/dist/adobe-analytics.js +2 -0
  5. package/dist/ahrefs-analytics.js +2 -0
  6. package/dist/amplitude.js +2 -0
  7. package/dist/clearbit.js +2 -0
  8. package/dist/cloudflare-web-analytics.js +2 -0
  9. package/dist/cloudflare-zaraz.js +2 -0
  10. package/dist/crisp.js +2 -0
  11. package/dist/databuddy.js +2 -0
  12. package/dist/e2e-test-utils.js +2 -139
  13. package/dist/engine/compile.js +2 -89
  14. package/dist/engine/runtime.js +2 -448
  15. package/dist/events.js +2 -218
  16. package/dist/fathom-analytics.js +2 -0
  17. package/dist/front-chat.js +2 -0
  18. package/dist/google-tag-manager.js +2 -0
  19. package/dist/google-tag.js +2 -0
  20. package/dist/heap.js +2 -0
  21. package/dist/hightouch.js +2 -0
  22. package/dist/hotjar.js +2 -0
  23. package/dist/intercom.js +2 -0
  24. package/dist/klaviyo.js +2 -0
  25. package/dist/linkedin-insights.js +2 -0
  26. package/dist/logrocket.js +2 -0
  27. package/dist/matomo-analytics.js +2 -0
  28. package/dist/meta-pixel.js +2 -0
  29. package/dist/microsoft-clarity.js +2 -0
  30. package/dist/microsoft-uet.js +2 -0
  31. package/dist/mixpanel-analytics.js +2 -0
  32. package/dist/one-dollar-stats.js +2 -0
  33. package/dist/openai-pixel.js +2 -0
  34. package/dist/pinterest-tag.js +2 -0
  35. package/dist/pirsch.js +2 -0
  36. package/dist/plausible-analytics.js +2 -0
  37. package/dist/posthog.js +2 -0
  38. package/dist/promptwatch.js +2 -0
  39. package/dist/reddit-pixel.js +2 -0
  40. package/dist/registry.js +2 -422
  41. package/dist/resolve.js +2 -33
  42. package/dist/rudderstack.js +2 -0
  43. package/dist/rybbit-analytics.js +2 -0
  44. package/dist/segment.js +2 -0
  45. package/dist/snapchat-pixel.js +2 -0
  46. package/dist/tiktok-pixel.js +2 -0
  47. package/dist/types.js +2 -16
  48. package/dist/umami-analytics.js +2 -0
  49. package/dist/vendors/_shared/attributes.js +2 -14
  50. package/dist/vendors/_shared/google-consent.js +2 -27
  51. package/dist/vendors/_shared/install-builders.js +2 -21
  52. package/dist/vendors/_shared/required-id.js +2 -0
  53. package/dist/vendors/_shared/script-url.js +2 -28
  54. package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
  55. package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
  56. package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
  57. package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
  58. package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -123
  59. package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
  60. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
  61. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
  62. package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
  63. package/dist/vendors/analytics/adobe-analytics.js +2 -49
  64. package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
  65. package/dist/vendors/analytics/amplitude.js +2 -134
  66. package/dist/vendors/analytics/clearbit.js +2 -28
  67. package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
  68. package/dist/vendors/analytics/databuddy.js +2 -103
  69. package/dist/vendors/analytics/fathom-analytics.js +2 -35
  70. package/dist/vendors/analytics/google-tag.js +2 -78
  71. package/dist/vendors/analytics/heap.js +2 -134
  72. package/dist/vendors/analytics/hightouch.js +2 -109
  73. package/dist/vendors/analytics/hotjar.js +2 -44
  74. package/dist/vendors/analytics/logrocket.js +2 -58
  75. package/dist/vendors/analytics/matomo-analytics.js +2 -191
  76. package/dist/vendors/analytics/microsoft-clarity.js +2 -100
  77. package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
  78. package/dist/vendors/analytics/one-dollar-stats.js +2 -30
  79. package/dist/vendors/analytics/pirsch.js +2 -67
  80. package/dist/vendors/analytics/plausible-analytics.js +2 -81
  81. package/dist/vendors/analytics/posthog.js +2 -200
  82. package/dist/vendors/analytics/promptwatch.js +2 -29
  83. package/dist/vendors/analytics/rudderstack.js +2 -183
  84. package/dist/vendors/analytics/rybbit-analytics.js +2 -63
  85. package/dist/vendors/analytics/segment.js +2 -65
  86. package/dist/vendors/analytics/umami-analytics.js +2 -39
  87. package/dist/vendors/analytics/vercel-analytics.js +2 -53
  88. package/dist/vendors/email-and-sms/klaviyo.js +2 -0
  89. package/dist/vendors/functional/crisp.js +2 -100
  90. package/dist/vendors/functional/front-chat.js +2 -64
  91. package/dist/vendors/functional/intercom.js +2 -45
  92. package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
  93. package/dist/vendors/tag-managers/google-tag-manager.js +2 -73
  94. package/dist/vercel-analytics.js +2 -0
  95. package/dist/x-pixel.js +2 -0
  96. package/dist-types/adobe-analytics.d.ts +2 -0
  97. package/dist-types/ahrefs-analytics.d.ts +2 -0
  98. package/dist-types/amplitude.d.ts +2 -0
  99. package/dist-types/clearbit.d.ts +2 -0
  100. package/dist-types/cloudflare-web-analytics.d.ts +2 -0
  101. package/dist-types/cloudflare-zaraz.d.ts +2 -0
  102. package/dist-types/crisp.d.ts +2 -0
  103. package/dist-types/databuddy.d.ts +2 -0
  104. package/dist-types/e2e-test-utils.d.ts +2 -0
  105. package/dist-types/engine/compile.d.ts +2 -3
  106. package/dist-types/engine/runtime.d.ts +2 -3
  107. package/dist-types/events.d.ts +2 -46
  108. package/dist-types/fathom-analytics.d.ts +2 -0
  109. package/dist-types/front-chat.d.ts +2 -0
  110. package/dist-types/google-tag-manager.d.ts +2 -0
  111. package/dist-types/google-tag.d.ts +2 -0
  112. package/dist-types/heap.d.ts +2 -0
  113. package/dist-types/hightouch.d.ts +2 -0
  114. package/dist-types/hotjar.d.ts +2 -0
  115. package/dist-types/intercom.d.ts +2 -0
  116. package/dist-types/klaviyo.d.ts +2 -0
  117. package/dist-types/linkedin-insights.d.ts +2 -0
  118. package/dist-types/logrocket.d.ts +2 -0
  119. package/dist-types/matomo-analytics.d.ts +2 -0
  120. package/dist-types/meta-pixel.d.ts +2 -0
  121. package/dist-types/microsoft-clarity.d.ts +2 -0
  122. package/dist-types/microsoft-uet.d.ts +2 -0
  123. package/dist-types/mixpanel-analytics.d.ts +2 -0
  124. package/dist-types/one-dollar-stats.d.ts +2 -0
  125. package/dist-types/openai-pixel.d.ts +2 -0
  126. package/dist-types/pinterest-tag.d.ts +2 -0
  127. package/dist-types/pirsch.d.ts +2 -0
  128. package/dist-types/plausible-analytics.d.ts +2 -0
  129. package/dist-types/posthog.d.ts +2 -0
  130. package/dist-types/promptwatch.d.ts +2 -0
  131. package/dist-types/reddit-pixel.d.ts +2 -0
  132. package/dist-types/registry.d.ts +2 -485
  133. package/dist-types/resolve.d.ts +2 -9
  134. package/dist-types/rudderstack.d.ts +2 -0
  135. package/dist-types/rybbit-analytics.d.ts +2 -0
  136. package/dist-types/segment.d.ts +2 -0
  137. package/dist-types/snapchat-pixel.d.ts +2 -0
  138. package/dist-types/tiktok-pixel.d.ts +2 -0
  139. package/dist-types/types.d.ts +2 -314
  140. package/dist-types/umami-analytics.d.ts +2 -0
  141. package/dist-types/vendors/_shared/attributes.d.ts +2 -35
  142. package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
  143. package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
  144. package/dist-types/vendors/_shared/required-id.d.ts +2 -0
  145. package/dist-types/vendors/_shared/script-url.d.ts +2 -75
  146. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
  147. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
  148. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
  149. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
  150. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -295
  151. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
  152. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
  153. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
  154. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
  155. package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
  156. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
  157. package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
  158. package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
  159. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
  160. package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
  161. package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
  162. package/dist-types/vendors/analytics/google-tag.d.ts +2 -95
  163. package/dist-types/vendors/analytics/heap.d.ts +2 -316
  164. package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
  165. package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
  166. package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
  167. package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
  168. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
  169. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
  170. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -39
  171. package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
  172. package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
  173. package/dist-types/vendors/analytics/posthog.d.ts +2 -175
  174. package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
  175. package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
  176. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
  177. package/dist-types/vendors/analytics/segment.d.ts +2 -164
  178. package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
  179. package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
  180. package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
  181. package/dist-types/vendors/functional/crisp.d.ts +2 -78
  182. package/dist-types/vendors/functional/front-chat.d.ts +2 -62
  183. package/dist-types/vendors/functional/intercom.d.ts +2 -135
  184. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
  185. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -96
  186. package/dist-types/vercel-analytics.d.ts +2 -0
  187. package/dist-types/x-pixel.d.ts +2 -0
  188. package/docs/README.md +129 -63
  189. package/docs/assets/v3/bottom-bar.png +0 -0
  190. package/docs/assets/v3/brand-card.png +0 -0
  191. package/docs/assets/v3/brand-preferences.png +0 -0
  192. package/docs/assets/v3/choice-wall.png +0 -0
  193. package/docs/assets/v3/headless-bar-html.png +0 -0
  194. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  195. package/docs/assets/v3/headless-bar.png +0 -0
  196. package/docs/assets/v3/slim-bar.png +0 -0
  197. package/docs/concepts/choose-your-setup.md +87 -0
  198. package/docs/concepts/consent-categories.md +84 -0
  199. package/docs/{guides → concepts}/consent-state.md +89 -105
  200. package/docs/{guides → concepts}/data-fetching.md +31 -27
  201. package/docs/concepts/how-consent-works.md +123 -0
  202. package/docs/concepts/policies.md +71 -0
  203. package/docs/customization/class-names.md +202 -0
  204. package/docs/customization/dark-mode.md +157 -0
  205. package/docs/customization/motion.md +119 -0
  206. package/docs/customization/overview.md +67 -34
  207. package/docs/customization/recipes.md +839 -49
  208. package/docs/customization/slots.md +216 -35
  209. package/docs/customization/stylesheets.md +147 -0
  210. package/docs/customization/tailwind.md +842 -0
  211. package/docs/customization/tokens.md +163 -96
  212. package/docs/customization/translations.md +60 -3
  213. package/docs/frameworks/astro/embeds.md +160 -0
  214. package/docs/frameworks/astro/network-blocker.md +86 -0
  215. package/docs/frameworks/astro/scripts.md +155 -0
  216. package/docs/frameworks/html/embeds.md +142 -0
  217. package/docs/frameworks/html/network-blocker.md +105 -0
  218. package/docs/frameworks/html/scripts.md +164 -0
  219. package/docs/frameworks/javascript/scripts.md +137 -0
  220. package/docs/frameworks/next/embeds.md +90 -0
  221. package/docs/frameworks/next/network-blocker.md +153 -0
  222. package/docs/frameworks/next/scripts.md +196 -0
  223. package/docs/frameworks/nuxt/embeds.md +81 -0
  224. package/docs/frameworks/nuxt/network-blocker.md +97 -0
  225. package/docs/frameworks/nuxt/scripts.md +89 -0
  226. package/docs/frameworks/react/embeds.md +89 -0
  227. package/docs/frameworks/react/network-blocker.md +140 -0
  228. package/docs/frameworks/react/scripts.md +115 -0
  229. package/docs/frameworks/svelte/embeds.md +96 -0
  230. package/docs/frameworks/svelte/network-blocker.md +141 -0
  231. package/docs/frameworks/svelte/scripts.md +144 -0
  232. package/docs/frameworks/sveltekit/embeds.md +103 -0
  233. package/docs/frameworks/sveltekit/network-blocker.md +159 -0
  234. package/docs/frameworks/sveltekit/scripts.md +172 -0
  235. package/docs/frameworks/tanstack-start/embeds.md +96 -0
  236. package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
  237. package/docs/frameworks/tanstack-start/scripts.md +103 -0
  238. package/docs/frameworks/vue/embeds.md +84 -0
  239. package/docs/frameworks/vue/network-blocker.md +99 -0
  240. package/docs/frameworks/vue/scripts.md +93 -0
  241. package/docs/guides/banner-experiments.md +654 -0
  242. package/docs/guides/troubleshooting.md +120 -47
  243. package/docs/guides/verify-consent.md +81 -49
  244. package/docs/integrations/adobe-analytics.md +167 -159
  245. package/docs/integrations/ahrefs-analytics.md +152 -154
  246. package/docs/integrations/amplitude.md +162 -156
  247. package/docs/integrations/building-integrations.md +136 -37
  248. package/docs/integrations/clearbit.md +154 -154
  249. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  250. package/docs/integrations/cloudflare-zaraz.md +209 -261
  251. package/docs/integrations/crisp.md +164 -158
  252. package/docs/integrations/databuddy.md +157 -173
  253. package/docs/integrations/fathom-analytics.md +158 -156
  254. package/docs/integrations/front-chat.md +167 -167
  255. package/docs/integrations/google-maps.md +118 -83
  256. package/docs/integrations/google-tag-manager.md +178 -163
  257. package/docs/integrations/google-tag.md +163 -160
  258. package/docs/integrations/heap.md +163 -155
  259. package/docs/integrations/hightouch.md +161 -157
  260. package/docs/integrations/hotjar.md +159 -155
  261. package/docs/integrations/intercom.md +183 -153
  262. package/docs/integrations/klaviyo.md +486 -0
  263. package/docs/integrations/linkedin-insights.md +174 -150
  264. package/docs/integrations/logrocket.md +160 -156
  265. package/docs/integrations/matomo-analytics.md +188 -178
  266. package/docs/integrations/meta-pixel.md +188 -150
  267. package/docs/integrations/microsoft-clarity.md +163 -155
  268. package/docs/integrations/microsoft-uet.md +148 -154
  269. package/docs/integrations/mixpanel-analytics.md +155 -160
  270. package/docs/integrations/one-dollar-stats.md +166 -165
  271. package/docs/integrations/openai-pixel.md +204 -301
  272. package/docs/integrations/overview.md +137 -80
  273. package/docs/integrations/pinterest-tag.md +191 -183
  274. package/docs/integrations/pirsch.md +169 -159
  275. package/docs/integrations/plausible-analytics.md +172 -158
  276. package/docs/integrations/posthog.md +313 -240
  277. package/docs/integrations/promptwatch.md +154 -154
  278. package/docs/integrations/reddit-pixel.md +185 -157
  279. package/docs/integrations/rudderstack.md +201 -186
  280. package/docs/integrations/rybbit-analytics.md +171 -160
  281. package/docs/integrations/segment.md +182 -154
  282. package/docs/integrations/snapchat-pixel.md +186 -156
  283. package/docs/integrations/tiktok-pixel.md +171 -150
  284. package/docs/integrations/umami-analytics.md +163 -158
  285. package/docs/integrations/vercel-analytics.md +166 -157
  286. package/docs/integrations/x-pixel.md +176 -150
  287. package/docs/integrations/youtube.md +121 -86
  288. package/docs/upgrade-v3.md +496 -467
  289. package/package.json +10 -257
  290. package/dist-types/__tests__/helpers.d.ts +0 -141
  291. package/docs/assets/v3/brand-bar.png +0 -0
  292. package/docs/assets/v3/mobile-card.png +0 -0
  293. package/docs/assets/v3/preferences.png +0 -0
  294. package/docs/frameworks/javascript/script-loader.md +0 -100
  295. package/docs/frameworks/next/script-loader.md +0 -216
  296. package/docs/frameworks/react/script-loader.md +0 -69
  297. package/docs/guides/deployment-modes.md +0 -75
  298. package/docs/guides/shared-consent-controls.md +0 -158
  299. package/docs/integrations/clear-on-revocation.md +0 -167
  300. package/docs/integrations/granular-consent.md +0 -210
@@ -1,88 +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
- The stylesheet blocks rendering, so it carries only what a first paint can
15
- show: the default tokens, every c15t CSS variable, and the rules for the
16
- banner, `ConsentDialogTrigger` and the `ConsentGate` placeholder. The consent
17
- dialog and preference widget bring their own rules. Each rule reaches the page
18
- once.
19
-
20
- ```tsx
21
- import 'c15t/react/styles.css';
22
- ```
23
-
24
- | File | Holds | Loaded by |
25
- | -------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
26
- | `styles.css` or `styles.tw3.css` | Default tokens, all variables, banner, trigger and `ConsentGate` rules | Your app, once |
27
- | `@c15t/ui/styles/dialog.css` | Dialog and preference widget rules | The dialog component, with its lazy chunk |
28
- | `@c15t/ui/styles/primitives.css` | Rules for the `@c15t/ui/styles/primitives` class maps | Svelte's `styles.css`, or your app if it renders those class maps |
29
- | `iab/styles.css` | IAB TCF banner and dialog rules and variables | Your app, after `styles.css` |
30
-
31
- In React, Next.js and TanStack Start, the dialog's module imports
32
- `@c15t/ui/styles/dialog.css`. The bundler emits it with the dialog's chunk and
33
- loads it before that chunk runs, so the dialog never renders unstyled. The
34
- chunk loads after first paint, by the time the dialog first opens. Do not
35
- import `dialog.css` yourself. The `@c15t/react/components/consent-dialog`,
36
- `/components/consent-widget`, `/primitives`, `/primitives/*` and `/iab` entry
37
- points import it as soon as you import them, because they render dialog parts
38
- outside the lazy chunk.
39
-
40
- These modules import the stylesheet through `@c15t/ui/styles/dialog`. Under
41
- the `node` export condition that module imports nothing, so server code that
42
- loads `@c15t/react` with plain Node, such as the Pages Router or an SSR build
43
- that keeps dependencies external, does not fail on the `.css` file. In your
44
- own components, import `@c15t/ui/styles/dialog` rather than the `.css` file
45
- if the module can run on the server.
46
-
47
- Svelte loads its dialog with the page, so `c15t/svelte/styles.css` also
48
- imports the dialog and primitive rules. Astro injects the banner rules; the
49
- React and Svelte dialog islands import the rest, and Astro links those
50
- stylesheets on every page, so in Astro the dialog rules still block rendering.
51
- Vue components import their own stylesheets.
52
-
53
- The dialog rules sit in `@layer components` and declare no variables, so
54
- tokens from `theme`, variables you override in your own CSS, and Tailwind 4
55
- utilities still win over them even though they load later. If you import
56
- `styles.css` into a named layer, such as `@import 'c15t/react/styles.css'
57
- layer(c15t)`, the dialog rules still join the top-level `components` layer.
58
- List `components` in your layer order statement, for example
59
- `@layer c15t, components, app;`, so it does not land after your own layers.
60
-
61
- The standard stylesheet places rules in `@layer components`. For Tailwind 3,
62
- use the `styles.tw3.css` entry instead of the standard stylesheet, between the
63
- components and utilities directives in your Tailwind entry:
64
-
65
- ```css
66
- @tailwind base;
67
- @tailwind components;
68
- @import 'c15t/react/styles.tw3.css';
69
- @tailwind utilities;
70
- ```
71
-
72
- Tailwind 3 also processes the dialog stylesheet the bundler loads, and it
73
- rejects a stylesheet that uses `@layer components` without its own
74
- `@tailwind components` directive. Add the c15t plugin before `tailwindcss` in
75
- your PostCSS config. It removes the layer wrapper from c15t's stylesheets so
76
- Tailwind 3 accepts them and its preflight does not override them:
77
-
78
- ```js title="postcss.config.mjs"
79
- export default {
80
- plugins: ['@c15t/ui/postcss-tailwind3', 'tailwindcss', 'autoprefixer'],
81
- };
82
- ```
83
-
84
- Do not load both c15t stylesheet variants. For Tailwind 4 or unlayered CSS,
85
- 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.
86
20
 
87
21
  ## Set semantic values together
88
22
 
@@ -94,27 +28,117 @@ readable in each state.
94
28
  import { defineTheme } from '@c15t/ui/theme';
95
29
 
96
30
  export const theme = defineTheme({
97
- colors: { primary: '#2f6f4e' },
98
- radius: { lg: '4px' },
99
- consentActions: {
100
- primary: { variant: 'primary', mode: 'filled' },
101
- dismiss: { variant: 'neutral', mode: 'stroke' },
102
- },
31
+ colors: { primary: '#2f6f4e' },
32
+ radius: { lg: '4px' },
33
+ consentActions: {
34
+ primary: { variant: 'primary', mode: 'filled' },
35
+ dismiss: { variant: 'neutral', mode: 'stroke' },
36
+ },
103
37
  });
104
38
  ```
105
39
 
106
- Install `@c15t/ui` if importing its theme helper directly. The browser does
107
- not turn tokens into CSS. In React and Next.js, render
108
- `<ConsentTheme theme={theme} />` where the app renders on the server, and pass
109
- `theme` in your provider options for `consentActions`. Elsewhere, call
110
- `generateThemeCSS(theme)` from `@c15t/ui/theme` on the server or at build time
111
- and put the result in a `<style>` element or your stylesheet. See
112
- [React styling](https://c15t.com/docs/frameworks/react/styling/overview) and
113
- [Next.js styling](https://c15t.com/docs/frameworks/next/styling/overview).
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.
114
55
 
115
56
  `consentActions` selects styling by action role. A per-action entry overrides
116
57
  `primary`, which overrides `default`.
117
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`.
141
+
118
142
  ## Target a prompt with CSS
119
143
 
120
144
  ```css
@@ -126,8 +150,9 @@ and put the result in a `<style>` element or your stylesheet. See
126
150
  ```
127
151
 
128
152
  Use attributes exposed by the rendered component, not guessed class names.
129
- Test the prompt and the preferences dialog separately because tokens scoped to
130
- 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.
131
156
 
132
157
  | Size variable | Default | Target |
133
158
  | ----------------------------------- | ------- | ------------- |
@@ -135,5 +160,47 @@ one prompt do not automatically reach a portaled dialog.
135
160
  | `--consent-banner-widget-max-width` | `20rem` | Widget |
136
161
  | `--consent-banner-wall-max-width` | `30rem` | Choice wall |
137
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
+
138
168
  Test long translations and small screens after changing width or typography.
139
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,47 @@ 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` or custom translations still applies. c15t reads it as `consentGate`, a key set under `consentGate`
72
+ wins over the same key under `frame`, and c15t logs a warning once outside
73
+ production. Rename the section to `consentGate` to remove the warning.
74
+
75
+ ## Show right-to-left languages
76
+
77
+ c15t sets `dir="rtl"` on its surfaces when the resolved language is Arabic,
78
+ Hebrew, Persian, Urdu, Pashto, Sindhi, Kurdish or Dhivehi. It matches the
79
+ primary language, so `ar-EG` counts as Arabic. Hebrew is the only
80
+ right-to-left translation c15t ships. For the others, supply your own
81
+ messages or the copy from your Inth project.
82
+
83
+ What follows the direction:
84
+
85
+ * Text, headings and the button row in the banner, the preference dialog and
86
+ the preference widget flow right to left.
87
+ * A floating or widget banner in its default corner moves to the mirrored
88
+ corner, so `bottom-left` becomes `bottom-right`. A `position` you set
89
+ yourself stays where you put it.
90
+ * The HTML script tag, `@c15t/browser` and Astro's banner also set `lang` on
91
+ their surfaces. React, Vue and Svelte set `dir` only, so set `lang` on
92
+ `<html>` yourself.
93
+
94
+ What does not follow yet:
95
+
96
+ * The IAB TCF dialog aligns several labels, indents and borders to the left.
97
+ * The floating `ConsentDialogTrigger` keeps the corner you give it.
98
+
99
+ Test right-to-left pages with a real translation before you ship them.
100
+
44
101
  ## Test more than English
45
102
 
46
103
  Try the longest labels you support at a narrow width, with browser zoom and
@@ -0,0 +1,160 @@
1
+ ---
2
+ title: Embeds
3
+ description: Gate YouTube videos, maps, social posts and other iframes on an
4
+ Astro site so they load only after the visitor allows their consent category,
5
+ with a custom element or the c15t iframe blocker.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Why an embed needs gating
10
+
11
+ An iframe sends requests to its host as soon as it is in the page with a
12
+ `src`, before any script can stop it. `ConsentBanner` does not block iframes
13
+ you already have. Render an embed only while its category is allowed, and
14
+ remove it when the visitor withdraws permission.
15
+
16
+ Astro has no consent gate component. Use one of these:
17
+
18
+ | Approach | Use it when |
19
+ | ------------------------------------------------------- | ---------------------------------------------------------- |
20
+ | A custom element that renders the iframe | You want a placeholder with a button in place of the embed |
21
+ | The iframe blocker, with `data-category` and `data-src` | You have iframe markup to gate as it is |
22
+
23
+ ## Gate an embed with a custom element
24
+
25
+ This component shows a placeholder with a preferences button until
26
+ measurement is allowed. It adds the iframe once the visitor allows
27
+ measurement, and removes it when permission is withdrawn. It also keeps the
28
+ iframe out while the visitor has switched YouTube off in
29
+ [vendor consent](https://c15t.com/docs/frameworks/astro/vendor-consent).
30
+
31
+ The component reads `client.isVendorAllowed('youtube')`, so declare `youtube`
32
+ in the `vendors` option of `c15t()` with `category: 'measurement'`. An
33
+ undeclared vendor reads as not allowed, and the video never loads:
34
+
35
+ ```astro title="src/components/consent-video.astro"
36
+ ---
37
+ import ConsentDialogTrigger from 'c15t/astro/components/consent-dialog-trigger.astro';
38
+ ---
39
+
40
+ <consent-video>
41
+ <div data-video>
42
+ <p>Allow measurement to load this YouTube video.</p>
43
+ </div>
44
+ <ConsentDialogTrigger>Open privacy settings</ConsentDialogTrigger>
45
+ </consent-video>
46
+
47
+ <script>
48
+ import { getConsentClient } from 'c15t/astro/client';
49
+
50
+ class ConsentVideo extends HTMLElement {
51
+ dispose?: () => void;
52
+
53
+ connect = () => {
54
+ const client = getConsentClient();
55
+ const container = this.querySelector('[data-video]');
56
+ if (this.dispose || !client || !container) {
57
+ return;
58
+ }
59
+ const render = () => {
60
+ // Measurement is allowed and the visitor has not switched YouTube
61
+ // off. An undeclared vendor is never allowed, so declare youtube.
62
+ if (!client.isVendorAllowed('youtube')) {
63
+ container.textContent =
64
+ 'Allow measurement to load this YouTube video. No video request is sent before permission.';
65
+ return;
66
+ }
67
+ if (container.querySelector('iframe')) {
68
+ return;
69
+ }
70
+ const frame = document.createElement('iframe');
71
+ frame.src = 'https://www.youtube-nocookie.com/embed/czTksCF6X8Y';
72
+ frame.title = 'YouTube video';
73
+ frame.allowFullscreen = true;
74
+ container.replaceChildren(frame);
75
+ };
76
+ render();
77
+ this.dispose = client.subscribe(render);
78
+ };
79
+
80
+ connectedCallback() {
81
+ // c15t boots from a module script, which can run after this one.
82
+ document.addEventListener('DOMContentLoaded', this.connect, {
83
+ once: true,
84
+ });
85
+ this.connect();
86
+ }
87
+
88
+ disconnectedCallback() {
89
+ document.removeEventListener('DOMContentLoaded', this.connect);
90
+ this.dispose?.();
91
+ this.dispose = undefined;
92
+ }
93
+ }
94
+
95
+ if (!customElements.get('consent-video')) {
96
+ customElements.define('consent-video', ConsentVideo);
97
+ }
98
+ </script>
99
+ ```
100
+
101
+ Use it like any Astro component. How it works:
102
+
103
+ * The iframe does not exist in the server HTML, so nothing loads before
104
+ consent, even before the consent runtime starts.
105
+ * `client.subscribe(render)` renders again on every consent change.
106
+ * `connectedCallback` runs again when `ClientRouter` swaps in a page that
107
+ contains the element, so the embed works across navigation.
108
+ * The first `connect()` can run before c15t has started, so the element tries
109
+ again on `DOMContentLoaded`.
110
+
111
+ Change the category, the iframe `src` and the placeholder text for other
112
+ embeds. The [YouTube](../../integrations/youtube.md) and
113
+ [Google Maps](../../integrations/google-maps.md) guides use the same pattern with
114
+ a reusable browser helper. [Integrations](../../integrations/overview.md) lists
115
+ the other vendors.
116
+
117
+ ## Gate existing iframe markup
118
+
119
+ The consent runtime includes an iframe blocker, on by default. Mark an iframe
120
+ with a category and move its URL from `src` to `data-src`:
121
+
122
+ ```html title="src/pages/contact.astro (partial)"
123
+ <iframe
124
+ data-category="marketing"
125
+ data-src="https://www.google.com/maps/embed?pb=..."
126
+ title="Office location"
127
+ ></iframe>
128
+ ```
129
+
130
+ When the category is allowed, the blocker copies `data-src` to `src`. When it
131
+ is withdrawn, the blocker removes `src` again. `data-vendor` gates the iframe
132
+ on one vendor as well. See [vendor consent](https://c15t.com/docs/frameworks/astro/vendor-consent).
133
+
134
+ Always use `data-src`, never `src`, for a gated iframe. The browser starts
135
+ loading a `src` from the HTML before the blocker runs, so an iframe with `src`
136
+ in the markup loads before consent.
137
+
138
+ The blocker watches the whole document, so it also gates iframes on pages you
139
+ reach with `ClientRouter`, which replaces `<body>` on each navigation.
140
+
141
+ ## Gate an embed on the server
142
+
143
+ On a server-rendered page, `Astro.locals.c15t.snapshot.effectivePermissions`
144
+ tells you whether the visitor had allowed the category when the request
145
+ arrived. Rendering the iframe on the server from it works for returning
146
+ visitors. A visitor who allows the category on the page sees the embed only
147
+ after the next navigation, so pair it with the custom element, or use the
148
+ custom element alone. See [Server API](https://c15t.com/docs/frameworks/astro/server).
149
+
150
+ ## Check the embeds
151
+
152
+ 1. Open the page in a private window with DevTools Network open. There is no
153
+ request to the embed's host, and no iframe from it in the Elements panel.
154
+ 2. Allow the embed's category from **Cookie preferences**. The iframe appears
155
+ and loads.
156
+ 3. Reload. The iframe loads again without a new choice.
157
+ 4. Withdraw the category and save. The page reloads, and the embed's host
158
+ gets no request.
159
+
160
+ See [Verify consent](../../guides/verify-consent.md) for the full checklist.
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Network blocker
3
+ description: Block fetch and XMLHttpRequest calls to tracking hosts on an Astro
4
+ site until the visitor allows their consent category, with c15t's network
5
+ blocker rules and an onRequestBlocked handler.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## What the network blocker does
10
+
11
+ `networkBlocker` stops `fetch` and `XMLHttpRequest` calls that match a rule
12
+ until the visitor allows the rule's category. A blocked `fetch` resolves to a
13
+ `451` response, and nothing is sent. It covers requests that code already on
14
+ the page makes, such as an SDK you load yourself or a tag manager's own calls.
15
+
16
+ It does not stop `navigator.sendBeacon`, WebSockets, image pixels, `<script>`
17
+ tags or iframes. Load scripts through c15t and gate iframes as
18
+ [Scripts](./scripts.md) and
19
+ [Embeds](./embeds.md) describe.
20
+
21
+ ## Add rules
22
+
23
+ Rules are plain data, so they can go in the integration options:
24
+
25
+ ```js title="astro.config.mjs (partial)"
26
+ c15t({
27
+ mode: hosted({ url: 'https://your-project.inth.app' }),
28
+ networkBlocker: {
29
+ rules: [
30
+ { category: 'measurement', domain: 'google-analytics.com' },
31
+ {
32
+ category: 'marketing',
33
+ domain: 'ads.example.com',
34
+ pathIncludes: '/collect',
35
+ methods: ['POST'],
36
+ },
37
+ ],
38
+ },
39
+ });
40
+ ```
41
+
42
+ | Rule field | Effect |
43
+ | -------------- | ----------------------------------------------------------------- |
44
+ | `category` | The category that must be allowed for the request to go through |
45
+ | `domain` | The host to match. It also matches every subdomain |
46
+ | `pathIncludes` | Matches only URLs whose path contains this text |
47
+ | `methods` | Matches only these HTTP methods |
48
+ | `vendor` | Also requires this vendor to be allowed, for vendor-level consent |
49
+
50
+ | Option | Default | Effect |
51
+ | -------------------- | -------- | ------------------------------------------------------------------- |
52
+ | `rules` | Required | The rules to apply |
53
+ | `enabled` | `true` | Set `false` to keep the rules but stop blocking |
54
+ | `logBlockedRequests` | `true` | Logs each blocked request to the console. Set `false` to silence it |
55
+
56
+ The blocker starts with the consent runtime, from a module script. A request
57
+ made before that, such as from an inline script at the top of `<head>`, is not
58
+ blocked.
59
+
60
+ ## Log or report blocked requests
61
+
62
+ `onRequestBlocked` is a function, so it cannot go in `astro.config.mjs`. Set
63
+ `networkBlocker` in the default export of your client entrypoint instead. It
64
+ replaces the integration's `networkBlocker` completely, so repeat the rules
65
+ there:
66
+
67
+ ```ts title="src/consent-client.ts (partial)"
68
+ export default {
69
+ scripts,
70
+ networkBlocker: {
71
+ rules: [{ category: 'measurement', domain: 'google-analytics.com' }],
72
+ onRequestBlocked: ({ url, rule }) => {
73
+ console.info('Blocked until consent:', url, rule?.category);
74
+ },
75
+ },
76
+ } satisfies C15tClientOptionsExtension;
77
+ ```
78
+
79
+ ## Check the network blocker
80
+
81
+ 1. Open the site in a private window with DevTools Network open, and trigger
82
+ the code that calls a blocked host. The request does not appear, and a
83
+ `fetch` receives a `451` response.
84
+ 2. Allow the rule's category. The next request to that host goes through.
85
+ 3. Withdraw the category and save. After the reload, requests to the host are
86
+ blocked again.