@c15t/nextjs 2.2.1 → 3.0.0-alpha.0

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 (220) hide show
  1. package/AGENTS.md +102 -142
  2. package/README.md +4 -4
  3. package/dist/api.js +1 -0
  4. package/dist/config.js +1 -0
  5. package/dist/devtools.js +2 -0
  6. package/dist/headers.js +1 -0
  7. package/dist/iab/styles.css +1 -1
  8. package/dist/iab/styles.tw3.css +133 -19
  9. package/dist/index.js +1 -1
  10. package/dist/middleware.js +1 -0
  11. package/dist/node-bridge.js +1 -0
  12. package/dist/pages.js +1 -0
  13. package/dist/proxy.js +1 -0
  14. package/dist/root.js +2 -0
  15. package/dist/server.js +1 -0
  16. package/dist/static.js +1 -0
  17. package/dist/styles.css +1 -1
  18. package/dist/styles.tw3.css +173 -29
  19. package/dist/version.js +1 -1
  20. package/dist-types/api.d.ts +88 -0
  21. package/dist-types/config.d.ts +110 -0
  22. package/dist-types/devtools.d.ts +1 -0
  23. package/dist-types/headers.d.ts +4 -0
  24. package/dist-types/index.d.ts +28 -10
  25. package/dist-types/middleware.d.ts +15 -0
  26. package/dist-types/node-bridge.d.ts +57 -0
  27. package/dist-types/pages.d.ts +88 -0
  28. package/dist-types/proxy.d.ts +34 -0
  29. package/dist-types/root.d.ts +86 -0
  30. package/dist-types/server.d.ts +164 -0
  31. package/dist-types/static.d.ts +42 -0
  32. package/dist-types/types.d.ts +5 -36
  33. package/dist-types/version.d.ts +1 -1
  34. package/docs/README.md +102 -142
  35. package/docs/assets/v3/brand-bar.png +0 -0
  36. package/docs/assets/v3/brand-card.png +0 -0
  37. package/docs/assets/v3/choice-wall.png +0 -0
  38. package/docs/assets/v3/mobile-card.png +0 -0
  39. package/docs/assets/v3/preferences.png +0 -0
  40. package/docs/customization/overview.md +45 -0
  41. package/docs/customization/recipes.md +79 -0
  42. package/docs/customization/slots.md +55 -0
  43. package/docs/customization/tokens.md +76 -0
  44. package/docs/customization/translations.md +49 -0
  45. package/docs/frameworks/next/api-reference/data-fetching.md +416 -0
  46. package/docs/frameworks/next/app-router.md +403 -0
  47. package/docs/frameworks/next/client-side.md +118 -0
  48. package/docs/frameworks/next/components/consent-banner.md +251 -211
  49. package/docs/frameworks/next/components/consent-dialog-link.md +96 -35
  50. package/docs/frameworks/next/components/consent-dialog-trigger.md +74 -149
  51. package/docs/frameworks/next/components/consent-dialog.md +189 -134
  52. package/docs/frameworks/next/components/consent-manager-provider.md +60 -318
  53. package/docs/frameworks/next/components/consent-widget.md +172 -114
  54. package/docs/frameworks/next/components/dev-tools.md +199 -40
  55. package/docs/frameworks/next/components/frame.md +137 -42
  56. package/docs/frameworks/next/concepts/consent-categories.md +24 -89
  57. package/docs/frameworks/next/concepts/policy-presets.md +142 -0
  58. package/docs/frameworks/next/content-security-policy.md +189 -0
  59. package/docs/frameworks/next/data-fetching.md +74 -0
  60. package/docs/frameworks/next/geography-headers.md +251 -0
  61. package/docs/frameworks/next/headless.md +95 -185
  62. package/docs/frameworks/next/hooks/use-consent-manager/overview.md +42 -163
  63. package/docs/frameworks/next/iab/overview.md +37 -107
  64. package/docs/frameworks/next/optimization.md +158 -194
  65. package/docs/frameworks/next/pages-router.md +296 -0
  66. package/docs/frameworks/next/quickstart.md +31 -132
  67. package/docs/frameworks/next/script-loader.md +134 -467
  68. package/docs/frameworks/next/server-side.md +97 -130
  69. package/docs/frameworks/next/static-export.md +164 -0
  70. package/docs/frameworks/next/styling/overview.md +166 -250
  71. package/docs/frameworks/next/troubleshooting.md +134 -144
  72. package/docs/guides/consent-state.md +60 -0
  73. package/docs/guides/data-fetching.md +163 -0
  74. package/docs/guides/deployment-modes.md +63 -0
  75. package/docs/guides/troubleshooting.md +68 -0
  76. package/docs/guides/verify-consent.md +62 -0
  77. package/docs/integrations/adobe-analytics.md +239 -105
  78. package/docs/integrations/ahrefs-analytics.md +238 -104
  79. package/docs/integrations/amplitude.md +219 -157
  80. package/docs/integrations/building-integrations.md +32 -224
  81. package/docs/integrations/clearbit.md +247 -86
  82. package/docs/integrations/cloudflare-web-analytics.md +250 -84
  83. package/docs/integrations/crisp.md +251 -97
  84. package/docs/integrations/databuddy.md +259 -153
  85. package/docs/integrations/fathom-analytics.md +239 -96
  86. package/docs/integrations/google-maps.md +328 -207
  87. package/docs/integrations/google-tag-manager.md +248 -96
  88. package/docs/integrations/google-tag.md +261 -90
  89. package/docs/integrations/heap.md +222 -149
  90. package/docs/integrations/hightouch.md +225 -131
  91. package/docs/integrations/hotjar.md +239 -90
  92. package/docs/integrations/intercom.md +239 -98
  93. package/docs/integrations/linkedin-insights.md +243 -113
  94. package/docs/integrations/logrocket.md +241 -123
  95. package/docs/integrations/matomo-analytics.md +256 -111
  96. package/docs/integrations/meta-pixel.md +197 -324
  97. package/docs/integrations/microsoft-clarity.md +233 -114
  98. package/docs/integrations/microsoft-uet.md +245 -110
  99. package/docs/integrations/mixpanel-analytics.md +252 -87
  100. package/docs/integrations/openai-pixel.md +441 -0
  101. package/docs/integrations/overview.md +95 -133
  102. package/docs/integrations/pirsch.md +249 -96
  103. package/docs/integrations/plausible-analytics.md +241 -100
  104. package/docs/integrations/posthog.md +353 -214
  105. package/docs/integrations/promptwatch.md +251 -81
  106. package/docs/integrations/reddit-pixel.md +226 -173
  107. package/docs/integrations/rudderstack.md +244 -187
  108. package/docs/integrations/rybbit-analytics.md +244 -91
  109. package/docs/integrations/segment.md +238 -92
  110. package/docs/integrations/snapchat-pixel.md +240 -110
  111. package/docs/integrations/tiktok-pixel.md +249 -81
  112. package/docs/integrations/umami-analytics.md +242 -95
  113. package/docs/integrations/vercel-analytics.md +242 -90
  114. package/docs/integrations/x-pixel.md +238 -104
  115. package/docs/integrations/youtube.md +354 -142
  116. package/docs/upgrade-v3.md +334 -0
  117. package/iab/styles.css +1 -1
  118. package/iab/styles.tw3.css +1 -1
  119. package/package.json +106 -65
  120. package/readme.json +3 -3
  121. package/src/iab/styles.css +1 -1
  122. package/src/iab/styles.tw3.css +1 -1
  123. package/src/styles.css +1 -1
  124. package/src/styles.tw3.css +1 -1
  125. package/styles.css +1 -1
  126. package/styles.tw3.css +1 -1
  127. package/client/components/consent-dialog-link.js +0 -3
  128. package/client/components/integrations.js +0 -3
  129. package/dist/components/integrations/index.cjs +0 -1
  130. package/dist/components/integrations/index.js +0 -1
  131. package/dist/headless.cjs +0 -1
  132. package/dist/index.cjs +0 -1
  133. package/dist/libs/browser-initial-data.cjs +0 -1
  134. package/dist/libs/browser-initial-data.js +0 -1
  135. package/dist/libs/initial-data.cjs +0 -1
  136. package/dist/libs/initial-data.js +0 -1
  137. package/dist/types.cjs +0 -1
  138. package/dist/version.cjs +0 -1
  139. package/dist-types/components/integrations/index.d.ts +0 -1
  140. package/dist-types/libs/browser-initial-data.d.ts +0 -9
  141. package/dist-types/libs/initial-data.d.ts +0 -33
  142. package/docs/frameworks/next/building-headless-components.md +0 -379
  143. package/docs/frameworks/next/callbacks.md +0 -186
  144. package/docs/frameworks/next/concepts/client-modes.md +0 -177
  145. package/docs/frameworks/next/concepts/consent-models.md +0 -117
  146. package/docs/frameworks/next/concepts/cookie-management.md +0 -122
  147. package/docs/frameworks/next/concepts/glossary.md +0 -24
  148. package/docs/frameworks/next/concepts/initialization-flow.md +0 -149
  149. package/docs/frameworks/next/concepts/policy-packs.md +0 -230
  150. package/docs/frameworks/next/hooks/use-color-scheme.md +0 -41
  151. package/docs/frameworks/next/hooks/use-consent-manager/checking-consent.md +0 -96
  152. package/docs/frameworks/next/hooks/use-consent-manager/location-info.md +0 -97
  153. package/docs/frameworks/next/hooks/use-consent-manager/setting-consent.md +0 -94
  154. package/docs/frameworks/next/hooks/use-draggable.md +0 -59
  155. package/docs/frameworks/next/hooks/use-focus-trap.md +0 -42
  156. package/docs/frameworks/next/hooks/use-reduced-motion.md +0 -37
  157. package/docs/frameworks/next/hooks/use-ssr-status.md +0 -32
  158. package/docs/frameworks/next/hooks/use-text-direction.md +0 -50
  159. package/docs/frameworks/next/hooks/use-translations.md +0 -55
  160. package/docs/frameworks/next/iab/consent-banner.md +0 -91
  161. package/docs/frameworks/next/iab/consent-dialog.md +0 -129
  162. package/docs/frameworks/next/iab/use-gvl-data.md +0 -21
  163. package/docs/frameworks/next/iframe-blocking.md +0 -106
  164. package/docs/frameworks/next/internationalization.md +0 -207
  165. package/docs/frameworks/next/network-blocker.md +0 -140
  166. package/docs/frameworks/next/policy-packs.md +0 -248
  167. package/docs/frameworks/next/styling/classnames.md +0 -94
  168. package/docs/frameworks/next/styling/color-scheme.md +0 -84
  169. package/docs/frameworks/next/styling/css-variables.md +0 -53
  170. package/docs/frameworks/next/styling/slots.md +0 -94
  171. package/docs/frameworks/next/styling/tailwind.md +0 -137
  172. package/docs/frameworks/next/styling/tokens.md +0 -156
  173. package/docs/shared/concepts/client-modes.md +0 -103
  174. package/docs/shared/concepts/consent-categories.md +0 -41
  175. package/docs/shared/concepts/consent-models.md +0 -72
  176. package/docs/shared/concepts/cookie-management.md +0 -88
  177. package/docs/shared/concepts/glossary.md +0 -24
  178. package/docs/shared/concepts/initialization-flow.md +0 -105
  179. package/docs/shared/concepts/policy-packs.md +0 -225
  180. package/docs/shared/react/components/consent-banner.md +0 -242
  181. package/docs/shared/react/components/consent-dialog-link.md +0 -45
  182. package/docs/shared/react/components/consent-dialog-trigger.md +0 -185
  183. package/docs/shared/react/components/consent-dialog.md +0 -119
  184. package/docs/shared/react/components/consent-manager-provider.md +0 -225
  185. package/docs/shared/react/components/consent-widget.md +0 -121
  186. package/docs/shared/react/components/dev-tools.md +0 -81
  187. package/docs/shared/react/components/frame.md +0 -52
  188. package/docs/shared/react/guides/building-headless-components.md +0 -110
  189. package/docs/shared/react/guides/callbacks.md +0 -89
  190. package/docs/shared/react/guides/headless.md +0 -31
  191. package/docs/shared/react/guides/iframe-blocking.md +0 -65
  192. package/docs/shared/react/guides/internationalization.md +0 -123
  193. package/docs/shared/react/guides/network-blocker.md +0 -72
  194. package/docs/shared/react/guides/optimization.md +0 -44
  195. package/docs/shared/react/guides/policy-packs.md +0 -173
  196. package/docs/shared/react/guides/script-loader.md +0 -311
  197. package/docs/shared/react/hooks/use-color-scheme.md +0 -31
  198. package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +0 -95
  199. package/docs/shared/react/hooks/use-consent-manager/location-info.md +0 -96
  200. package/docs/shared/react/hooks/use-consent-manager/overview.md +0 -74
  201. package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +0 -93
  202. package/docs/shared/react/hooks/use-draggable.md +0 -30
  203. package/docs/shared/react/hooks/use-focus-trap.md +0 -20
  204. package/docs/shared/react/hooks/use-reduced-motion.md +0 -33
  205. package/docs/shared/react/hooks/use-ssr-status.md +0 -16
  206. package/docs/shared/react/hooks/use-text-direction.md +0 -38
  207. package/docs/shared/react/hooks/use-translations.md +0 -15
  208. package/docs/shared/react/iab/consent-banner.md +0 -60
  209. package/docs/shared/react/iab/consent-dialog.md +0 -76
  210. package/docs/shared/react/iab/overview.md +0 -80
  211. package/docs/shared/react/iab/use-gvl-data.md +0 -21
  212. package/docs/shared/react/styling/classnames.md +0 -93
  213. package/docs/shared/react/styling/color-scheme.md +0 -35
  214. package/docs/shared/react/styling/css-variables.md +0 -53
  215. package/docs/shared/react/styling/overview.md +0 -261
  216. package/docs/shared/react/styling/slots.md +0 -93
  217. package/docs/shared/react/styling/stylesheet-entrypoint.md +0 -8
  218. package/docs/shared/react/styling/tailwind.md +0 -88
  219. package/docs/shared/react/styling/tokens.md +0 -155
  220. package/docs/shared/troubleshooting.md +0 -82
@@ -1,138 +1,196 @@
1
1
  ---
2
2
  title: ConsentWidget
3
- description: An inline consent management widget for embedding in settings or
4
- privacy pages. Shows category toggles with accordion layout.
3
+ description: Render ConsentWidget on a Next.js privacy page inside ConsentRoot
4
+ as an inline preference center, server-rendered when the route resolves
5
+ consent.
5
6
  group: frameworks
6
7
  ---
7
- `ConsentWidget` is a standalone, inline consent management widget. Unlike `ConsentDialog` (which is a modal), the widget embeds directly in your page layout - ideal for privacy settings pages, account preferences, or any page where users should be able to manage consent without a modal overlay.
8
8
 
9
- ## Basic Usage
9
+ ## Embed the preference center inline
10
10
 
11
- ```tsx
12
- import { ConsentWidget } from '@c15t/nextjs';
11
+ `ConsentWidget` renders the preference center in the page flow instead of in
12
+ a modal: the category switches and the Reject all, Accept all and Save
13
+ actions, without a title, backdrop or portal. Use it on a privacy or
14
+ settings route where the visitor expects the controls to be part of the
15
+ content. It is a Client Component; render it from any route under the
16
+ layout that mounts the `ConsentRoot` from your
17
+ [App Router](../app-router.md) or
18
+ [Pages Router](../pages-router.md) setup. Keep
19
+ `ConsentDialog` mounted in that root as well so the banner's Customize
20
+ button and preference links still have a dialog to open.
21
+
22
+ ```tsx title="app/privacy/page.tsx"
23
+ import { CookiePreferences } from '../../components/cookie-preferences';
13
24
 
14
- export function PrivacySettingsPage() {
25
+ export default function PrivacyPage() {
15
26
  return (
16
- <>
17
- <h1>Privacy Settings</h1>
18
- <p>Manage your cookie preferences below.</p>
19
- <ConsentWidget />
20
- </>
27
+ <main>
28
+ <h1>Privacy</h1>
29
+ <p>Choose which optional categories this site may use.</p>
30
+ <section aria-labelledby="cookie-preferences">
31
+ <h2 id="cookie-preferences">Cookie preferences</h2>
32
+ <CookiePreferences />
33
+ </section>
34
+ </main>
21
35
  );
22
36
  }
23
37
  ```
24
38
 
25
- ## Configuration
26
-
27
- ```tsx
28
- <ConsentWidget
29
- hideBranding
30
- legalLinks={['privacyPolicy', 'cookiePolicy']}
31
- noStyle={false}
32
- disableAnimation={false}
33
- />
34
- ```
35
-
36
- ## Accordion Behavior
37
-
38
- Each consent category is rendered as an expandable accordion item. Clicking the category header expands it to show a description and any associated services. Users can toggle individual categories on or off using the switch control. The `necessary` category is always enabled and cannot be toggled.
39
+ ```tsx title="components/cookie-preferences.tsx"
40
+ 'use client';
39
41
 
40
- ## Styling First
42
+ import { ConsentWidget } from 'c15t/next';
41
43
 
42
- > ℹ️ **Info:**
43
- > Most widget customization should stay in the stock component. Use theme tokens and slots such as consentWidgetAccordion, consentWidgetFooter, consentWidgetFooterSubGroup, and toggle before reaching for compound components. See Styling Overview.
44
-
45
- ```tsx
46
- <ConsentManagerProvider
47
- options={{
48
- theme: {
49
- colors: {
50
- surface: '#fffdf8',
51
- surfaceHover: '#f6f3ee',
52
- },
53
- slots: {
54
- consentWidgetAccordion: 'rounded-3xl border border-black/10',
55
- consentWidgetFooter: 'border-t border-black/10 px-6',
56
- consentWidgetFooterSubGroup: 'gap-3',
57
- toggle: 'shadow-sm',
58
- },
59
- },
60
- }}
61
- >
62
- <ConsentWidget />
63
- </ConsentManagerProvider>
44
+ export function CookiePreferences() {
45
+ return <ConsentWidget />;
46
+ }
64
47
  ```
65
48
 
66
- Use `consentWidgetFooter` for styling the whole action area, such as border and padding. Use `consentWidgetFooterSubGroup` for spacing or alignment inside nested button groups.
67
-
68
- Widget copy should be changed through `ConsentManagerProvider.options.i18n` so the inline UI stays aligned with the rest of the consent experience.
49
+ The page stays a Server Component; only the widget file needs
50
+ `'use client'`. On routes where `ConsentRoot` receives the visitor's resolved
51
+ state from an awaited `resolveConsent` (the App Router layout, or a
52
+ Pages Router page with `getServerSideProps`), the widget is part of the server
53
+ HTML with the visitor's recorded choices applied, unlike `ConsentDialog`,
54
+ which mounts after hydration. On a static or otherwise non-prefetched route the
55
+ widget renders after the browser resolves policy. The widget uses the
56
+ boundary's policy, translations and transport; it does not need a second
57
+ provider, and it does not open or close the banner or dialog by itself.
69
58
 
70
- ## Advanced: Compound Components
71
-
72
- Use compound components only when you need to rearrange the widget's existing primitives while keeping policy-aware action grouping:
73
-
74
- ```tsx
75
- <ConsentWidget.Root>
76
- <ConsentWidget.Accordion type="multiple">
77
- <ConsentWidget.AccordionItems />
78
- </ConsentWidget.Accordion>
79
- <ConsentWidget.PolicyActions />
80
- </ConsentWidget.Root>
81
- ```
59
+ ## Props
82
60
 
83
- * `ConsentWidget.Root` — Theme context provider
84
- * `ConsentWidget.Accordion` — Radix-based accordion root
85
- * `ConsentWidget.AccordionItems` — Auto-generates toggle items from consent config
86
- * `ConsentWidget.AccordionItem` — Individual category item
87
- * `ConsentWidget.AccordionTrigger` — Clickable header for each item
88
- * `ConsentWidget.AccordionContent` — Collapsible content area
89
- * `ConsentWidget.AccordionArrow` — Expand/collapse indicator
90
- * `ConsentWidget.Switch` — Category toggle switch
91
- * `ConsentWidget.PolicyActions` — Renders grouped policy-aware actions
92
- * `ConsentWidget.Footer` — Footer container
93
- * `ConsentWidget.FooterSubGroup` — Groups related buttons
94
- * `ConsentWidget.AcceptAllButton` — Accepts all consent
95
- * `ConsentWidget.RejectButton` — Rejects all consent
96
- * `ConsentWidget.SaveButton` — Saves custom selections
97
-
98
- ## Using `renderAction` with c15t Defaults
99
-
100
- `ConsentWidget.PolicyActions` renders stock c15t buttons and translations by default.
61
+ | Prop | Type | Default | Description |
62
+ | ------------------ | --------- | ---------- | ----------------------------------------------------------------------------------------------------------- |
63
+ | `hideBranding` | `boolean` | `true` | A standalone widget hides the "Secured by" tag. Pass `false` to show it. |
64
+ | `uiSource` | `string` | `'widget'` | Source identifier recorded with saves made from this widget. Inside `ConsentDialog` it inherits `'dialog'`. |
65
+ | `disableAnimation` | `boolean` | `false` | Skips animations in the widget's parts. |
66
+ | `noStyle` | `boolean` | `false` | Removes the built-in styling from every part. |
67
+
68
+ ## Behavior
69
+
70
+ The widget is the preference center without the modal around it: an
71
+ accordion with one row per category, each with a switch, followed by the
72
+ Reject all, Accept all and Save actions. `ConsentDialog` renders this same
73
+ widget inside its card. Standalone, the widget renders in place, takes part
74
+ in server rendering, and does not touch the active surface, so it suits a
75
+ privacy page or an account settings screen.
76
+
77
+ The rows are `necessary` plus the categories in the active policy's scope,
78
+ narrowed to the provider `options.consentCategories` when that list is non-empty.
79
+ The `necessary` switch is on and disabled. Row titles and descriptions come
80
+ from `consentTypes.<category>.title` and `.description`; the action labels
81
+ from `common.rejectAll`, `common.acceptAll` and `common.save`. One row is
82
+ expanded at a time.
83
+
84
+ ### Draft and save
85
+
86
+ Switches edit a draft, not the visitor's permissions. Nothing is recorded
87
+ until Save. The draft seeds each category from the recorded choice; without
88
+ one it uses `presentation.preferences.defaults` from the provider options,
89
+ and without that it is on under an opt-out rule and on only for the rule's
90
+ preselected categories under an opt-in rule. Save records the displayed
91
+ categories, then reseeds the draft from the new record. Accept all and
92
+ Reject all record immediately without a separate Save. A save answers a
93
+ choice prompt, so an open choice banner closes; it does not dismiss a
94
+ `notice` prompt, which keeps its own acknowledgement record. Unsaved toggles
95
+ are lost when the widget unmounts.
96
+
97
+ When the policy changes in a way that affects the choice while the draft has
98
+ unsaved toggles, the widget shows an alert, "The privacy policy changed.
99
+ Review the current choices before saving.", with a Review choices button
100
+ that resets the draft. Save is refused until then. A draft without unsaved
101
+ toggles reseeds silently.
102
+
103
+ A saved grant that the current policy or a privacy signal such as Global
104
+ Privacy Control overrides shows a note under its row: "Your saved choice is
105
+ restricted by the current privacy settings." The switch still reflects the
106
+ saved value; the effective permission is off.
107
+
108
+ ### Actions
109
+
110
+ The preference center always renders Reject all, Accept all and Save, with
111
+ Save as the primary action. `presentation.preferences.layout`,
112
+ `primaryActions`, `direction` and `uiProfile` in the provider options
113
+ reorder and restyle them; a layout that omits one of the three has it
114
+ restored. Customize and dismiss actions never render here.
115
+
116
+ ### When consent is not owed
117
+
118
+ Without a resolved policy rule the widget renders nothing and appears as
119
+ soon as a rule resolves, without a remount. A rule with `model: 'none'` and
120
+ an empty `rights` list renders nothing; a `none` rule that lists any right,
121
+ such as `['disclosure']`, renders the widget and Save completes without
122
+ writing a consent record.
123
+
124
+ For a fully custom preference center, `useConsentDraft()` returns the same
125
+ draft: `values`, `displayedCategories`, `isDirty`, `isStale`, `set`,
126
+ `update`, `acceptAll`, `rejectAll`, `save` and `reset`.
127
+
128
+ The `useConsentDraft` hook and `ConsentDraftProvider` component import from
129
+ `c15t/next`. Use them in Client Components.
130
+
131
+ ## Accessibility
132
+
133
+ Each row is a disclosure: a button that expands the category description
134
+ and a switch beside it, so a visitor can read the description without
135
+ changing the choice. Each switch has the category title as its accessible
136
+ name. The policy-change alert uses `role="alert"`, and a restriction note is
137
+ an `output` element linked to its switch through `aria-describedby`. The
138
+ root's `dir` attribute follows the active language.
139
+
140
+ ## Composition
141
+
142
+ Every part is available as `ConsentWidget.<Part>`. `Root` provides the
143
+ draft and accepts `noStyle`, `disableAnimation` and `uiSource`. `Accordion`
144
+ and `AccordionItems` render the stock rows; `AccordionItem`,
145
+ `AccordionTrigger`, `AccordionTriggerInner`, `AccordionContent`,
146
+ `AccordionArrow` and `Switch` build your own. `PolicyActions` renders the
147
+ resolved actions in `Footer` and `FooterSubGroup` and accepts `renderAction`
148
+ to replace one button; `AcceptAllButton`, `RejectButton`, `SaveButton` and
149
+ `CustomizeButton` are the individual buttons.
150
+
151
+ `Accordion` is controlled: pass `value` and `onValueChange`, or no
152
+ category description ever opens. The stock `ConsentWidget` holds that state
153
+ for you. In this component, `ConsentWidget` comes from the same import as
154
+ the example at the top of the page:
101
155
 
102
156
  ```tsx
103
- <ConsentWidget.PolicyActions />
104
- ```
157
+ import { useState } from 'react';
105
158
 
106
- `renderAction` is optional. Return the stock button compounds when you want custom mapping while preserving built-in c15t behavior and copy:
159
+ export function PreferenceCenter() {
160
+ const [open, setOpen] = useState<string[]>([]);
107
161
 
108
- ```tsx
109
- <ConsentWidget.PolicyActions
110
- renderAction={(action, props) => {
111
- const { key, ...buttonProps } = props
112
-
113
- switch (action) {
114
- case 'accept':
115
- return <ConsentWidget.AcceptAllButton key={key} {...buttonProps} />
116
- case 'reject':
117
- return <ConsentWidget.RejectButton key={key} {...buttonProps} />
118
- case 'customize':
119
- return <ConsentWidget.SaveButton key={key} {...buttonProps} />
120
- }
121
- }}
122
- />
162
+ return (
163
+ <ConsentWidget.Root>
164
+ <ConsentWidget.Accordion
165
+ value={open}
166
+ onValueChange={(next) => setOpen(Array.isArray(next) ? next : [next])}
167
+ >
168
+ <ConsentWidget.AccordionItems />
169
+ </ConsentWidget.Accordion>
170
+ <ConsentWidget.PolicyActions />
171
+ </ConsentWidget.Root>
172
+ );
173
+ }
123
174
  ```
124
175
 
125
- `renderAction` is still meant for stock button compounds. If you want completely custom button elements and handlers, use `useHeadlessConsentUI()` and render `dialog.actionGroups` manually instead of `ConsentWidget.PolicyActions`.
126
-
127
- For a fixed footer layout, render `ConsentWidget.Footer` and `ConsentWidget.FooterSubGroup` manually instead of using `ConsentWidget.PolicyActions`.
128
-
129
- If the stock widget structure is already correct, stay with tokens and slots instead of rebuilding the layout.
130
-
131
- ## Props
132
-
133
- |Property|Value|
134
- |:--|:--|
135
- |Type Name|\`ConsentWidgetProps\`|
136
- |Source Path|\`./packages/react/src/components/consent-widget/types.ts\`|
137
-
138
- \*ExtractedTypeTable: Could not extract "ConsentWidgetProps" from "./packages/react/src/components/consent-widget/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
176
+ Provider component slots for the stock structure are `manager.root`,
177
+ `manager.footer`, `manager.actionGroup`, `accordion.root`,
178
+ `accordion.triggerRow`, `accordion.title`, `accordion.control`,
179
+ `accordion-item.root`, `accordion-item.trigger`, `accordion-item.content`
180
+ and `tag.manager`.
181
+
182
+ The compound parts import from `c15t/next` as properties of `ConsentWidget`.
183
+ Server Components cannot render them: keep the composition in a file with
184
+ `'use client'`. See [Styling](../styling/overview.md) for slots and tokens.
185
+
186
+ ## Verify
187
+
188
+ Visit the page with the widget. Use a displayed optional category whose permission is
189
+ not restricted by policy or privacy signals such as GPC. One row per displayed
190
+ category appears, with `necessary` on and disabled. Turn that category on:
191
+ `useConsent('<category>')`
192
+ elsewhere on the page still reports the old value. Choose Save: it now
193
+ reports `true`. An open choice banner closes when the save answers its
194
+ prompt; a notice banner stays open until acknowledged. Reload the page: the
195
+ switch keeps the saved value. Without a saved choice or configured draft
196
+ defaults, optional switches start on under an opt-out rule.
@@ -1,64 +1,224 @@
1
1
  ---
2
2
  title: DevTools
3
- description: A development tool for inspecting consent state, geolocation,
4
- loaded scripts, and consent events in real time.
3
+ description: Load the c15t DevTools panel only in Next.js development builds to
4
+ inspect consent state, scripts, policy and events inside ConsentRoot.
5
5
  group: frameworks
6
6
  ---
7
- `DevTools` is a floating panel that shows the internal state of the consent manager. Use it during development to inspect consent values, geolocation results, loaded scripts, and debug consent flows. Also exported as `C15TDevTools` if you need to avoid naming conflicts with other devtools.
7
+
8
+ `DevTools` is a floating panel for the c15t v3 kernel. Use it during development to inspect consent values, scripts, location, the resolved policy, IAB state, events, and consent actions. The framework adapter reads the kernel from the nearest v3 provider. It never discovers a store through a window global.
8
9
 
9
10
  > ⚠️ **Warning:**
10
- > DevTools should only be included in development builds. The component renders nothing to the React tree - it injects directly into document.body.
11
+ > Load DevTools only in development. A conditional JSX expression stops it from mounting in production, but a static import can still add DevTools to the production bundle. Use a development-only dynamic import as shown below. The component renders nothing into the React tree and mounts its panel in document.body.
11
12
 
12
13
  ## Installation
13
14
 
14
- DevTools lives in a separate package to keep it out of production bundles:
15
-
16
- ```bash
17
- bun add -D @c15t/dev-tools
18
- ```
15
+ No extra package is required for a framework adapter. The adapter and its
16
+ engine are included with the `c15t` package and the dedicated
17
+ `@c15t/svelte` adapter. Install `@c15t/dev-tools` directly only
18
+ when you need the imperative `createDevTools({ kernel })` API.
19
19
 
20
20
  ## Usage
21
21
 
22
- ```tsx
23
- import { type ReactNode } from 'react';
24
- import { ConsentManagerProvider } from '@c15t/nextjs';
25
- import { DevTools } from '@c15t/dev-tools/react';
22
+ Keep the `ConsentRoot` from your router setup. Add this client component as
23
+ one of its children, alongside the banner and dialog. It uses the existing
24
+ runtime, including its prefetch result and configured transport.
26
25
 
27
- export function ConsentManager({ children }: { children: ReactNode }) {
28
- return (
29
- <ConsentManagerProvider options={{ mode: 'hosted', backendURL: '/api/c15t' }}>
30
- {children}
31
- {process.env.NODE_ENV === 'development' && <DevTools />}
32
- </ConsentManagerProvider>
33
- );
26
+ ```tsx title="components/consent-dev-tools.tsx"
27
+ 'use client';
28
+
29
+ import dynamic from 'next/dynamic';
30
+
31
+ const DevTools =
32
+ process.env.NODE_ENV === 'development'
33
+ ? dynamic(() => import('c15t/next/devtools').then(({ DevTools }) => DevTools), {
34
+ ssr: false,
35
+ })
36
+ : () => null;
37
+
38
+ export function ConsentDevTools() {
39
+ return <DevTools />;
34
40
  }
35
41
  ```
36
42
 
43
+ Import `ConsentDevTools` into your existing consent wrapper or App Router layout
44
+ and render `<ConsentDevTools />` inside `ConsentRoot`. Keep its `state`,
45
+ `config` and other props unchanged. A setup that uses `ConsentProvider`
46
+ directly can place the same component inside that provider.
47
+
48
+ The umbrella import is `c15t/next/devtools`. When installing the dedicated
49
+ Next.js package, use `@c15t/nextjs/devtools`.
50
+
37
51
  ## Configuration
38
52
 
39
53
  ```tsx
40
54
  <DevTools
41
- position="bottom-right" // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
42
- defaultOpen={false} // Start with panel open
43
- namespace="c15tStore" // Store namespace to connect to
44
- disabled={false} // Disable without removing from tree
55
+ position="bottom-right"
56
+ defaultOpen={false}
57
+ defaultTab="consents"
58
+ maxEvents={100}
59
+ disabled={false}
45
60
  />
46
61
  ```
47
62
 
63
+ `position` accepts any corner: `top-left`, `top-right`, `bottom-left`, or
64
+ `bottom-right`. Set `defaultOpen` to open the initial panel and `defaultTab`
65
+ to choose that panel. `maxEvents` limits captured kernel and script events.
66
+ Set `disabled` to prevent mounting without removing the component.
67
+
48
68
  ## Panels
49
69
 
50
- |Panel|What it shows|
51
- |--|--|
52
- |**Consents**|Current consent state for all categories|
53
- |**Location**|Detected jurisdiction, country, region, and consent model|
54
- |**Scripts**|Configured scripts and their load status|
55
- |**IAB**|IAB TCF state (when enabled) - TC string, vendor consents, purposes|
56
- |**Events**|Timeline of consent events and state changes|
57
- |**Actions**|Buttons to trigger consent actions (accept all, reject all, reset)|
70
+ Accept and reject apply only to categories displayed by the provider, leaving
71
+ hidden consent values unchanged. `necessary` always remains enabled. Svelte
72
+ and Vue adapters use their provider's displayed categories; the kernel and
73
+ React adapter use the current policy categories by default.
74
+
75
+ For an imperative integration with a narrower UI, pass
76
+ `getConsentCategories: () => ['necessary', 'measurement']` to
77
+ `createDevTools`. The getter is read again when an action runs. Headless UI
78
+ actions can use `kernel.commands.save('all', { categories: displayed })` or
79
+ `kernel.commands.save('none', { categories: displayed })`. These scoped bulk
80
+ saves use a `custom` transport action when they cover only part of the policy.
81
+ Actions covering the whole policy retain `all` or `necessary`. Without an
82
+ explicit scope or policy categories, bulk saves cover all known categories.
83
+
84
+ | Panel | What it shows |
85
+ | ------------ | -------------------------------------------------------------------------------------------- |
86
+ | **Consents** | Inspect and save categories, accept all, or reject optional categories |
87
+ | **Scripts** | Configured scripts, loading status, search, and external page resources |
88
+ | **Location** | Detected jurisdiction, country, region, and consent model |
89
+ | **Policy** | Resolved policy and UI configuration |
90
+ | **IAB** | Edit vendors, purposes, legitimate interests, and special features; save and copy TC strings |
91
+ | **Events** | Timeline of consent changes, kernel events, and script lifecycle events |
92
+ | **Actions** | Show the banner, open preferences, hide consent UI, or refresh consent data |
93
+
94
+ ### Script inspection
95
+
96
+ Set `defaultTab="scripts"` to open script inspection first. It reads all script
97
+ loaders attached to the current provider's kernel, including loaders created
98
+ before DevTools mounts. Search by script ID, category, URL, or status, then
99
+ expand a script to inspect its configuration and latest lifecycle event.
100
+
101
+ `loading` means an external script was inserted; `loaded` means its browser
102
+ load event fired, or an inline script or callback-only integration mounted.
103
+ `error` reports a loader error. `blocked` means consent requirements were not
104
+ met, and `pending` means an eligible script has not mounted. `present` means
105
+ the loader reused an element without a confirmed load result. `retained`
106
+ means consent was revoked but `persistAfterConsentRevoked` kept the element.
107
+ Retained scripts still receive `onConsentChange` with `hasConsent: false`, so
108
+ their integrations can send the vendor's consent-revocation command.
109
+ `alwaysLoad` bypasses the loading gate, not consent: callbacks receive the
110
+ actual consent state and updates across categories for integrations such as
111
+ Google Consent Mode.
112
+
113
+ Script details distinguish `allowedToLoad` from `consentGranted`. An
114
+ `alwaysLoad` integration can be allowed to load while its consent is denied.
115
+ Diagnostics expose these as `eligible` and `hasConsent`, respectively.
116
+
117
+ Lifecycle diagnostics work even when legacy debug forwarding is disabled.
118
+ The page scan lists external scripts and iframes present in the
119
+ DOM; it does not prove that they loaded successfully or were consent-gated.
120
+
121
+ For custom inspection tools, import `getScriptDiagnostics(kernel)` and
122
+ `subscribeScriptDiagnostics(kernel, listener)` from
123
+ `c15t/modules/script-loader`. The subscription reports loader
124
+ registration, updates, disposal, and lifecycle events. Read a fresh snapshot
125
+ after changes and call the returned unsubscribe function during cleanup.
126
+
127
+ ### IAB editing
128
+
129
+ Under an IAB policy, the Consents tab is read-only. Edit and save vendors
130
+ and purposes in the IAB tab so the derived categories and TC string stay
131
+ consistent.
132
+
133
+ The IAB panel connects to the existing `@c15t/iab` module attached to the
134
+ provider's kernel. It does not create a second CMP or replace `__tcfapi`.
135
+ Controls become available after initialization, when the current policy uses
136
+ IAB and the vendor list is loaded.
137
+
138
+ Choose Vendors, Purposes, or Special features, then search by name or ID.
139
+ Vendors include the provider's custom vendors. Long lists show 20 entries per
140
+ page. Legitimate-interest controls appear for declared legitimate interests.
141
+ Search only filters the view; Accept all IAB and Reject all IAB apply to the
142
+ configured choices, including custom vendors.
143
+
144
+ Toggles update live IAB state and script gating immediately. Use Save IAB
145
+ consent to generate a fresh TC string and run the configured save transport.
146
+ The panel displays the last generated string; unsaved edits are not represented
147
+ in it. Copy TC string confirms success or reports clipboard failures. Raw IAB
148
+ data remains available in an expandable section.
149
+
150
+ IAB saves normally write the TC string to its standard cookie and localStorage.
151
+ For an in-memory playground, set `persistence: false` in the IAB module options
152
+ as well as disabling the provider's core persistence. The IAB option prevents
153
+ TC-string storage writes; it does not disable the configured save transport
154
+ or remove previously stored values.
155
+
156
+ Save and refresh actions show pending, success, and failure feedback. Controls
157
+ are disabled while a request is pending. A failed save does not roll back the
158
+ live choices; retry to record them.
159
+
160
+ For custom inspection tools, `getIABControls(kernel)` and
161
+ `subscribeIABControls(kernel, listener)` are exported from `c15t`.
162
+ The getter returns undefined before module initialization and after disposal.
163
+ A snapshot without an attached IAB module is read-only in DevTools.
164
+
165
+ ## Vue and Svelte
166
+
167
+ Vue imports `ConsentDevTools` from `c15t/vue/devtools`.
168
+ Render it in the app where the c15t plugin provides
169
+ the kernel. In Nuxt, the configured c15t module provides that context.
170
+
171
+ ```vue
172
+ <script setup lang="ts">
173
+ import { defineAsyncComponent } from 'vue';
174
+
175
+ const DevTools = import.meta.env.DEV
176
+ ? defineAsyncComponent(() => import('c15t/vue/devtools'))
177
+ : null;
178
+ </script>
179
+
180
+ <template>
181
+ <DevTools v-if="DevTools" position="bottom-right" />
182
+ </template>
183
+ ```
184
+
185
+ In Nuxt, use `import.meta.dev` instead of `import.meta.env.DEV`.
186
+ Svelte imports the adapter from `@c15t/svelte/devtools` and renders it
187
+ inside `ConsentManagerProvider`.
188
+
189
+ ```svelte
190
+ <script lang="ts">
191
+ import { ConsentManagerProvider, offline } from '@c15t/svelte';
192
+
193
+ const devTools = import.meta.env.DEV
194
+ ? import('@c15t/svelte/devtools')
195
+ : null;
196
+ </script>
197
+
198
+ <ConsentManagerProvider options={{ mode: offline() }}>
199
+ {#if devTools}
200
+ {#await devTools then { ConsentDevTools }}
201
+ <ConsentDevTools position="bottom-right" />
202
+ {/await}
203
+ {/if}
204
+ </ConsentManagerProvider>
205
+ ```
206
+
207
+ Both adapters accept `position`, `defaultOpen`, `defaultTab`, `maxEvents`,
208
+ `shadow` (`false` renders into the page instead of a shadow root),
209
+ and `getConsentCategories`. Changing these presentation options recreates
210
+ the panel and clears its event history. They do not accept React's `disabled`
211
+ prop; conditionally render them instead. Unmounting removes the panel and
212
+ subscriptions without changing consent.
58
213
 
59
214
  ## TanStack Devtools
60
215
 
61
- `@c15t/dev-tools/tanstack` exposes a panel component and plugin factory that match TanStack Devtools' plugin API, so c15t can sit beside Query and Router without a custom mount adapter:
216
+ Each embedded panel owns its event history and subscriptions. Unmounting it
217
+ destroys the instance; remounting starts a new history. If you need to capture
218
+ events while switching plugins, keep the c15t panel mounted. This differs from
219
+ the old globally shared DevTools store.
220
+
221
+ The React v3 DevTools adapter exports a panel component and plugin factory that match TanStack Devtools' plugin API:
62
222
 
63
223
  ```tsx
64
224
  import * as React from 'react';
@@ -66,7 +226,7 @@ import { useRouter } from '@tanstack/react-router';
66
226
  import { TanStackDevtools } from '@tanstack/react-devtools';
67
227
  import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
68
228
  import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
69
- import { c15tDevtools } from '@c15t/dev-tools/tanstack';
229
+ import { c15tDevtools } from 'c15t/react/devtools';
70
230
 
71
231
  export function AppDevtools() {
72
232
  const router = useRouter();
@@ -89,11 +249,10 @@ export function AppDevtools() {
89
249
  }
90
250
  ```
91
251
 
92
- ## Props
252
+ In a Next.js app, import `c15tDevtools` from `c15t/next/devtools` instead. Scoped installs use `@c15t/react/devtools` and `@c15t/nextjs/devtools`.
93
253
 
94
- |Property|Value|
95
- |:--|:--|
96
- |Type Name|\`C15TDevToolsProps\`|
97
- |Source Path|\`./packages/dev-tools/src/react.ts\`|
254
+ ## Props
98
255
 
99
- \*ExtractedTypeTable: Could not extract "C15TDevToolsProps" from "./packages/dev-tools/src/react.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
256
+ | Property | Type | Description | Default | Required |
257
+ | :------- | :------------------ | :------------------------------------------ | :------ | :------: |
258
+ | disabled | boolean \|undefined | Prevents the DevTools engine from mounting. | false | Optional |