@c15t/nextjs 2.2.1 → 3.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/AGENTS.md +103 -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 +27 -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 +67 -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 +92 -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 +103 -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 +68 -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 +140 -465
  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 +174 -248
  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/clear-on-revocation.md +167 -0
  82. package/docs/integrations/clearbit.md +247 -86
  83. package/docs/integrations/cloudflare-web-analytics.md +250 -84
  84. package/docs/integrations/crisp.md +251 -97
  85. package/docs/integrations/databuddy.md +259 -153
  86. package/docs/integrations/fathom-analytics.md +239 -96
  87. package/docs/integrations/google-maps.md +328 -207
  88. package/docs/integrations/google-tag-manager.md +248 -96
  89. package/docs/integrations/google-tag.md +261 -90
  90. package/docs/integrations/heap.md +222 -149
  91. package/docs/integrations/hightouch.md +225 -131
  92. package/docs/integrations/hotjar.md +239 -90
  93. package/docs/integrations/intercom.md +239 -98
  94. package/docs/integrations/linkedin-insights.md +243 -113
  95. package/docs/integrations/logrocket.md +241 -123
  96. package/docs/integrations/matomo-analytics.md +256 -111
  97. package/docs/integrations/meta-pixel.md +197 -324
  98. package/docs/integrations/microsoft-clarity.md +233 -114
  99. package/docs/integrations/microsoft-uet.md +245 -110
  100. package/docs/integrations/mixpanel-analytics.md +252 -87
  101. package/docs/integrations/openai-pixel.md +441 -0
  102. package/docs/integrations/overview.md +95 -133
  103. package/docs/integrations/pirsch.md +249 -96
  104. package/docs/integrations/plausible-analytics.md +241 -100
  105. package/docs/integrations/posthog.md +353 -214
  106. package/docs/integrations/promptwatch.md +251 -81
  107. package/docs/integrations/reddit-pixel.md +226 -173
  108. package/docs/integrations/rudderstack.md +244 -187
  109. package/docs/integrations/rybbit-analytics.md +244 -91
  110. package/docs/integrations/segment.md +238 -92
  111. package/docs/integrations/snapchat-pixel.md +240 -110
  112. package/docs/integrations/tiktok-pixel.md +249 -81
  113. package/docs/integrations/umami-analytics.md +242 -95
  114. package/docs/integrations/vercel-analytics.md +242 -90
  115. package/docs/integrations/x-pixel.md +238 -104
  116. package/docs/integrations/youtube.md +354 -142
  117. package/docs/upgrade-v3.md +334 -0
  118. package/iab/styles.css +1 -1
  119. package/iab/styles.tw3.css +1 -1
  120. package/package.json +106 -65
  121. package/readme.json +3 -3
  122. package/src/iab/styles.css +1 -1
  123. package/src/iab/styles.tw3.css +1 -1
  124. package/src/styles.css +1 -1
  125. package/src/styles.tw3.css +1 -1
  126. package/styles.css +1 -1
  127. package/styles.tw3.css +1 -1
  128. package/client/components/consent-dialog-link.js +0 -3
  129. package/client/components/integrations.js +0 -3
  130. package/dist/components/integrations/index.cjs +0 -1
  131. package/dist/components/integrations/index.js +0 -1
  132. package/dist/headless.cjs +0 -1
  133. package/dist/index.cjs +0 -1
  134. package/dist/libs/browser-initial-data.cjs +0 -1
  135. package/dist/libs/browser-initial-data.js +0 -1
  136. package/dist/libs/initial-data.cjs +0 -1
  137. package/dist/libs/initial-data.js +0 -1
  138. package/dist/types.cjs +0 -1
  139. package/dist/version.cjs +0 -1
  140. package/dist-types/components/integrations/index.d.ts +0 -1
  141. package/dist-types/libs/browser-initial-data.d.ts +0 -9
  142. package/dist-types/libs/initial-data.d.ts +0 -33
  143. package/docs/frameworks/next/building-headless-components.md +0 -379
  144. package/docs/frameworks/next/callbacks.md +0 -186
  145. package/docs/frameworks/next/concepts/client-modes.md +0 -177
  146. package/docs/frameworks/next/concepts/consent-models.md +0 -117
  147. package/docs/frameworks/next/concepts/cookie-management.md +0 -122
  148. package/docs/frameworks/next/concepts/glossary.md +0 -24
  149. package/docs/frameworks/next/concepts/initialization-flow.md +0 -149
  150. package/docs/frameworks/next/concepts/policy-packs.md +0 -230
  151. package/docs/frameworks/next/hooks/use-color-scheme.md +0 -41
  152. package/docs/frameworks/next/hooks/use-consent-manager/checking-consent.md +0 -96
  153. package/docs/frameworks/next/hooks/use-consent-manager/location-info.md +0 -97
  154. package/docs/frameworks/next/hooks/use-consent-manager/setting-consent.md +0 -94
  155. package/docs/frameworks/next/hooks/use-draggable.md +0 -59
  156. package/docs/frameworks/next/hooks/use-focus-trap.md +0 -42
  157. package/docs/frameworks/next/hooks/use-reduced-motion.md +0 -37
  158. package/docs/frameworks/next/hooks/use-ssr-status.md +0 -32
  159. package/docs/frameworks/next/hooks/use-text-direction.md +0 -50
  160. package/docs/frameworks/next/hooks/use-translations.md +0 -55
  161. package/docs/frameworks/next/iab/consent-banner.md +0 -91
  162. package/docs/frameworks/next/iab/consent-dialog.md +0 -129
  163. package/docs/frameworks/next/iab/use-gvl-data.md +0 -21
  164. package/docs/frameworks/next/iframe-blocking.md +0 -106
  165. package/docs/frameworks/next/internationalization.md +0 -207
  166. package/docs/frameworks/next/network-blocker.md +0 -140
  167. package/docs/frameworks/next/policy-packs.md +0 -248
  168. package/docs/frameworks/next/styling/classnames.md +0 -94
  169. package/docs/frameworks/next/styling/color-scheme.md +0 -84
  170. package/docs/frameworks/next/styling/css-variables.md +0 -53
  171. package/docs/frameworks/next/styling/slots.md +0 -94
  172. package/docs/frameworks/next/styling/tailwind.md +0 -137
  173. package/docs/frameworks/next/styling/tokens.md +0 -156
  174. package/docs/shared/concepts/client-modes.md +0 -103
  175. package/docs/shared/concepts/consent-categories.md +0 -41
  176. package/docs/shared/concepts/consent-models.md +0 -72
  177. package/docs/shared/concepts/cookie-management.md +0 -88
  178. package/docs/shared/concepts/glossary.md +0 -24
  179. package/docs/shared/concepts/initialization-flow.md +0 -105
  180. package/docs/shared/concepts/policy-packs.md +0 -225
  181. package/docs/shared/react/components/consent-banner.md +0 -242
  182. package/docs/shared/react/components/consent-dialog-link.md +0 -45
  183. package/docs/shared/react/components/consent-dialog-trigger.md +0 -185
  184. package/docs/shared/react/components/consent-dialog.md +0 -119
  185. package/docs/shared/react/components/consent-manager-provider.md +0 -225
  186. package/docs/shared/react/components/consent-widget.md +0 -121
  187. package/docs/shared/react/components/dev-tools.md +0 -81
  188. package/docs/shared/react/components/frame.md +0 -52
  189. package/docs/shared/react/guides/building-headless-components.md +0 -110
  190. package/docs/shared/react/guides/callbacks.md +0 -89
  191. package/docs/shared/react/guides/headless.md +0 -31
  192. package/docs/shared/react/guides/iframe-blocking.md +0 -65
  193. package/docs/shared/react/guides/internationalization.md +0 -123
  194. package/docs/shared/react/guides/network-blocker.md +0 -72
  195. package/docs/shared/react/guides/optimization.md +0 -44
  196. package/docs/shared/react/guides/policy-packs.md +0 -173
  197. package/docs/shared/react/guides/script-loader.md +0 -311
  198. package/docs/shared/react/hooks/use-color-scheme.md +0 -31
  199. package/docs/shared/react/hooks/use-consent-manager/checking-consent.md +0 -95
  200. package/docs/shared/react/hooks/use-consent-manager/location-info.md +0 -96
  201. package/docs/shared/react/hooks/use-consent-manager/overview.md +0 -74
  202. package/docs/shared/react/hooks/use-consent-manager/setting-consent.md +0 -93
  203. package/docs/shared/react/hooks/use-draggable.md +0 -30
  204. package/docs/shared/react/hooks/use-focus-trap.md +0 -20
  205. package/docs/shared/react/hooks/use-reduced-motion.md +0 -33
  206. package/docs/shared/react/hooks/use-ssr-status.md +0 -16
  207. package/docs/shared/react/hooks/use-text-direction.md +0 -38
  208. package/docs/shared/react/hooks/use-translations.md +0 -15
  209. package/docs/shared/react/iab/consent-banner.md +0 -60
  210. package/docs/shared/react/iab/consent-dialog.md +0 -76
  211. package/docs/shared/react/iab/overview.md +0 -80
  212. package/docs/shared/react/iab/use-gvl-data.md +0 -21
  213. package/docs/shared/react/styling/classnames.md +0 -93
  214. package/docs/shared/react/styling/color-scheme.md +0 -35
  215. package/docs/shared/react/styling/css-variables.md +0 -53
  216. package/docs/shared/react/styling/overview.md +0 -261
  217. package/docs/shared/react/styling/slots.md +0 -93
  218. package/docs/shared/react/styling/stylesheet-entrypoint.md +0 -8
  219. package/docs/shared/react/styling/tailwind.md +0 -88
  220. package/docs/shared/react/styling/tokens.md +0 -155
  221. package/docs/shared/troubleshooting.md +0 -82
@@ -1,169 +1,224 @@
1
1
  ---
2
2
  title: ConsentDialog
3
- description: A modal dialog where users can toggle individual consent categories.
3
+ description: Mount ConsentDialog as a Client Component inside a Next.js
4
+ ConsentRoot to open the preference center from the banner, links and triggers.
4
5
  group: frameworks
5
6
  ---
6
- `ConsentDialog` is a modal that shows toggles for each consent category. In **opt-in jurisdictions**, it typically opens when users click "Customize" on `ConsentBanner`. It can also be controlled programmatically for use on settings pages regardless of jurisdiction.
7
7
 
8
- ## Basic Usage
9
-
10
- ```tsx
11
- import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';
12
-
13
- export function ConsentManager() {
8
+ ## Open the preference center
9
+
10
+ `ConsentDialog` is the modal preference center. It is a Client Component, so
11
+ it lives in the `components/consent.tsx` wrapper from the
12
+ [App Router](../app-router.md) or
13
+ [Pages Router](../pages-router.md) setup, inside the
14
+ `ConsentRoot` that already provides the consent runtime. Mount it once,
15
+ next to the banner; it opens whenever something makes `dialog` the active
16
+ surface: the banner's Customize button, a `ConsentDialogLink` in your footer,
17
+ a `ConsentDialogTrigger`, or your own code.
18
+
19
+ ```tsx title="components/consent.tsx"
20
+ 'use client';
21
+
22
+ import type { ReactNode } from 'react';
23
+ import {
24
+ ConsentBanner,
25
+ ConsentDialog,
26
+ ConsentDialogLink,
27
+ ConsentRoot,
28
+ } from 'c15t/next';
29
+ import type { ConsentRootProps } from 'c15t/next';
30
+ import { consentConfig } from '../c15t.config';
31
+
32
+ export function Consent({
33
+ children,
34
+ state,
35
+ }: {
36
+ children: ReactNode;
37
+ state: ConsentRootProps['state'];
38
+ }) {
14
39
  return (
15
- <ConsentManagerProvider options={{ mode: 'hosted', backendURL: '/api/c15t' }}>
40
+ <ConsentRoot state={state} config={consentConfig}>
41
+ {children}
16
42
  <ConsentBanner />
17
- <ConsentDialog />
18
- </ConsentManagerProvider>
43
+ <ConsentDialog hideBranding />
44
+ <footer>
45
+ <ConsentDialogLink>Privacy settings</ConsentDialogLink>
46
+ </footer>
47
+ </ConsentRoot>
19
48
  );
20
49
  }
21
50
  ```
22
51
 
23
- ## Controlled State
52
+ The root layout renders `Consent` with the `state` from
53
+ `resolveConsent`, as in the router guides. The dialog itself renders
54
+ nothing on the server: it mounts through a portal after hydration, so the
55
+ server HTML and the streamed shell are unaffected by it. If the visitor owes
56
+ a choice or notice, routes that await `resolveConsent` can render the
57
+ banner on the server. Pages Router pages without `getServerSideProps` show it
58
+ after browser policy resolution. No banner appears when the policy requires
59
+ no prompt or an existing record already satisfies it.
24
60
 
25
- By default, the dialog follows `activeUI === 'dialog'` from the consent store. Use the `open` prop for manual control:
61
+ To open it from another Client Component inside the boundary, set the active
62
+ surface:
26
63
 
27
- ```tsx
28
- import { useState } from 'react';
64
+ ```tsx title="components/privacy-menu-item.tsx"
65
+ 'use client';
29
66
 
30
- function SettingsPage() {
31
- const [open, setOpen] = useState(false);
67
+ import { useSetActiveUI } from 'c15t/next';
32
68
 
69
+ export function PrivacyMenuItem() {
70
+ const setActiveUI = useSetActiveUI();
33
71
  return (
34
- <>
35
- <button onClick={() => setOpen(true)}>Privacy Settings</button>
36
- <ConsentDialog open={open} />
37
- </>
38
- );
39
- }
40
- ```
41
-
42
- Or use the hook to open it programmatically:
43
-
44
- ```tsx
45
- import { useConsentManager } from '@c15t/nextjs';
46
-
47
- function PrivacyLink() {
48
- const { setActiveUI } = useConsentManager();
49
-
50
- return (
51
- <button onClick={() => setActiveUI('dialog')}>
52
- Manage cookies
72
+ <button type="button" onClick={() => setActiveUI('dialog')}>
73
+ Cookie preferences
53
74
  </button>
54
75
  );
55
76
  }
56
77
  ```
57
78
 
58
- ## Floating Trigger
59
-
60
- Add a floating button that lets users re-open the dialog after dismissing the banner:
61
-
62
- ```tsx
63
- {/* Default trigger */}
64
- <ConsentDialog showTrigger />
65
-
66
- {/* Custom trigger */}
67
- <ConsentDialog
68
- showTrigger={{
69
- icon: 'settings',
70
- defaultPosition: 'bottom-left',
71
- showWhen: 'after-consent',
72
- size: 'sm',
73
- }}
74
- />
75
- ```
76
-
77
- ## Branding
78
-
79
- Hide the c15t branding tag:
80
-
81
- ```tsx
82
- <ConsentDialog hideBranding />
83
- ```
84
-
85
- ## Styling First
86
-
87
- > ℹ️ **Info:**
88
- > If you are only changing visuals, stay with the stock dialog and use the theme system first. Start with tokens and slots such as consentDialogCard, consentWidgetFooter, and consentDialogTag. See Styling Overview.
89
-
90
- ```tsx
91
- <ConsentManagerProvider
92
- options={{
93
- theme: {
94
- colors: {
95
- surface: '#fffdf8',
96
- surfaceHover: '#f6f3ee',
97
- },
98
- slots: {
99
- consentDialogCard: 'rounded-[32px] shadow-xl',
100
- consentDialogHeader: 'gap-3',
101
- consentWidgetFooter: 'gap-3 pt-6',
102
- consentDialogTag: 'shadow-none',
103
- },
104
- },
105
- }}
106
- >
107
- <ConsentDialog />
108
- </ConsentManagerProvider>
109
- ```
79
+ `ConsentDialogLink` does the same with the policy's rights exposed for
80
+ styling; see [ConsentDialogLink](./consent-dialog-link.md). For a floating
81
+ button, see [ConsentDialogTrigger](./consent-dialog-trigger.md). Under an IAB
82
+ policy, mount `IABConsentDialog` from the [IAB guide](../iab/overview.md)
83
+ instead; this dialog stays closed for the `iab` model.
110
84
 
111
- Dialog copy should be changed through `ConsentManagerProvider.options.i18n`, not by rebuilding the dialog structure.
112
-
113
- ## Advanced: Compound Components
85
+ ## Props
114
86
 
115
- Use compound components only when you need custom dialog markup while still keeping c15t primitives and policy-aware footer actions:
87
+ | Prop | Type | Default | Description |
88
+ | ------------------------- | ------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
89
+ | `open` | `boolean` | follows the active surface | Controls the open state. When set, Escape and the dialog's own Save, Accept all and Reject all buttons no longer close it; change the prop instead. A missing policy or an unlisted model still keeps it closed. |
90
+ | `showTrigger` | `boolean \|ConsentDialogTriggerProps` | `false` | Renders a floating `ConsentDialogTrigger` next to the dialog. Pass an object to configure that trigger. |
91
+ | `models` | `Model[]` | `['opt-in', 'opt-out', 'none']` | Policy models the dialog responds to. Under a model that is not listed, such as `iab`, it stays closed. |
92
+ | `legalLinks` | `(keyof LegalLinks)[] \|null` | none | Which of the links configured in the provider `options.legalLinks` render after the description. Omitting the prop renders none. `null` hides them. |
93
+ | `hideBranding` | `boolean` | `false` | Hides the "Secured by" tag in the card. |
94
+ | `uiSource` | `string` | `'dialog'` | Source identifier recorded with saves made from this dialog. |
95
+ | `scrollLock`, `trapFocus` | `boolean` | from `blocking` | Deprecated. Either one set to `false` makes the dialog non-blocking, unless the provider sets `presentation.preferences.blocking` explicitly, which wins. Prefer the provider option. |
96
+ | `disableAnimation` | `boolean` | `false` | Skips the enter and exit animation of the backdrop and the card. |
97
+ | `noStyle` | `boolean` | `false` | Removes the built-in styling from every part. |
98
+
99
+ ## Behavior
100
+
101
+ The dialog is a modal wrapper around the preference center. Its card has a
102
+ header with the title `consentManagerDialog.title` and the description
103
+ `consentManagerDialog.description` followed by the legal links, then the
104
+ same category accordion and Reject all, Accept all and Save actions that
105
+ `ConsentWidget` renders, then the branding tag. It mounts through a portal
106
+ into `document.body` after hydration, so it is never part of the server
107
+ HTML.
108
+
109
+ Without an `open` prop, the dialog follows the active surface and opens while
110
+ it is `dialog`. These set that surface:
111
+
112
+ * The Customize button on a `ConsentBanner`, and the "Manage preferences"
113
+ or "Do not sell or share my data" button a notice renders.
114
+ * `ConsentDialogLink` and `ConsentDialogTrigger`.
115
+ * The button in a `Frame` placeholder.
116
+ * `useSetActiveUI()('dialog')` in your own component, or `openDialog()`
117
+ from `useHeadlessConsentUI()` on the headless subpath.
118
+
119
+ The component code is split into its own chunk. It downloads when the
120
+ dialog first opens or when a visitor hovers or focuses a Customize button,
121
+ and renders nothing until that chunk is ready. Passing `open={true}` or
122
+ `showTrigger` loads it on mount.
123
+
124
+ Without an `open` prop, Escape closes the dialog. Save, Accept all and
125
+ Reject all close it after the save succeeds; a save that fails keeps it
126
+ open. With `open={true}`, these actions leave the dialog visible; the
127
+ parent must set `open={false}` to close it. Clicking the backdrop does not
128
+ close it. Closing discards toggles that were not saved: the next open starts
129
+ from the recorded choice again. After a save from an uncontrolled dialog,
130
+ every consent surface hides unless the policy still owes a prompt, in which
131
+ case the banner returns.
132
+
133
+ The preference center is blocking by default: a backdrop, body scroll lock
134
+ and focus trap, all as one value. Set `presentation.preferences.blocking`
135
+ to `false` in the provider options to remove all three; the deprecated
136
+ `scrollLock` and `trapFocus` props do the same for one dialog. `variant`
137
+ and `position` are prompt options. Setting them under
138
+ `presentation.preferences` logs an `invalid-variant` diagnostic in
139
+ development and changes nothing; the dialog is always centered.
140
+
141
+ The dialog never opens without a resolved policy rule, even when the
142
+ active surface is already `dialog`; it appears as soon as a rule resolves,
143
+ without a remount. A rule with `model: 'none'` and no rights owes no consent
144
+ UI, so the dialog stays closed under it. When such a rule lists any right,
145
+ for example `rights: ['disclosure']` or `rights: ['preferences']`, the dialog
146
+ can open as a settings route and Save completes without writing a consent
147
+ record.
148
+
149
+ Copy comes from these translation keys: `consentManagerDialog.title`,
150
+ `consentManagerDialog.description`, `common.acceptAll`, `common.rejectAll`,
151
+ `common.save`, and `consentTypes.<category>.title` and `.description` for
152
+ each row.
153
+
154
+ ## Accessibility
155
+
156
+ The panel carries `role="dialog"`, `aria-labelledby="consent-dialog-title"`
157
+ and `aria-describedby="consent-dialog-description"`, plus `aria-modal="true"`
158
+ while it is blocking. Its `dir` attribute follows the active language.
159
+
160
+ While blocking, focus moves on open to the panel itself rather than to a
161
+ button, so a screen reader announces the title and description first; Tab
162
+ then enters the legal links and category switches, Tab and Shift+Tab wrap
163
+ inside the panel, and on close focus returns to the element that opened the
164
+ dialog. A non-blocking dialog manages no focus: nothing moves focus into the
165
+ panel or back to the opener. The backdrop is `aria-hidden` and is not
166
+ focusable.
167
+
168
+ Each category switch has the category title as its accessible name. The
169
+ `necessary` switch is disabled and always on. A saved grant that the current
170
+ policy or a privacy signal overrides gets a note under its row, linked to
171
+ the switch through `aria-describedby`.
172
+
173
+ ## Composition
174
+
175
+ Every part is available as `ConsentDialog.<Part>`: `Root`, `Overlay`,
176
+ `Card`, `Header`, `HeaderTitle`, `HeaderDescription`, `Content`, `Footer`
177
+ and `ConsentCustomizationCard`, the stock card. `Root` provides the portal,
178
+ open state, focus trap, scroll lock and backdrop, and accepts `open`,
179
+ `models`, `noStyle`, `disableAnimation`, `scrollLock`, `trapFocus`,
180
+ `uiSource` and `overlay`. Pass `overlay={false}` to render no backdrop, or a
181
+ node to replace the built-in one.
116
182
 
117
183
  ```tsx
118
184
  <ConsentDialog.Root>
119
- <ConsentDialog.Overlay />
120
185
  <ConsentDialog.Card>
121
186
  <ConsentDialog.Header>
122
- <ConsentDialog.HeaderTitle />
123
- <ConsentDialog.HeaderDescription />
187
+ <ConsentDialog.HeaderTitle>Your privacy choices</ConsentDialog.HeaderTitle>
188
+ <ConsentDialog.HeaderDescription legalLinks={['privacyPolicy']} />
124
189
  </ConsentDialog.Header>
125
190
  <ConsentDialog.Content>
126
- <ConsentWidget.Root>
127
- <ConsentWidget.Accordion type="single">
128
- <ConsentWidget.AccordionItems />
129
- </ConsentWidget.Accordion>
130
- <ConsentWidget.PolicyActions />
131
- </ConsentWidget.Root>
191
+ <ConsentWidget />
132
192
  </ConsentDialog.Content>
133
- <ConsentDialog.Footer />
193
+ <ConsentDialog.Footer hideBranding />
134
194
  </ConsentDialog.Card>
135
195
  </ConsentDialog.Root>
136
196
  ```
137
197
 
138
- * `ConsentDialog.Root` — Portal container with focus trap, scroll lock, and animation
139
- * `ConsentDialog.Card` — Main dialog card
140
- * `ConsentDialog.Header` — Contains title and description
141
- * `ConsentDialog.HeaderTitle` — Dialog title
142
- * `ConsentDialog.HeaderDescription` — Description with optional `legalLinks`
143
- * `ConsentDialog.Content` — Main content area (typically contains `ConsentWidget`)
144
- * `ConsentDialog.Footer` — Footer with optional branding (`hideBranding` prop)
145
- * `ConsentDialog.Overlay` — Backdrop overlay
146
- * `ConsentWidget.PolicyActions` — Renders policy-aware grouped dialog actions
147
-
148
- For a quick pre-composed layout, use the shorthand card:
149
-
150
- ```tsx
151
- <ConsentDialog.Root>
152
- <ConsentDialog.ConsentCustomizationCard />
153
- </ConsentDialog.Root>
154
- ```
155
-
156
- `ConsentWidget.PolicyActions` uses stock c15t widget buttons and translations by default. Pass `renderAction` only when you need to customize the action mapping, and return stock widget button compounds if you want to preserve built-in behavior and copy.
157
-
158
- For fully manual control over dialog action rendering, use `useHeadlessConsentUI()` and map `dialog.actionGroups` yourself.
159
-
160
- If the stock dialog structure still works, prefer tokens, slots, and provider configuration instead.
161
-
162
- ## Props
163
-
164
- |Property|Value|
165
- |:--|:--|
166
- |Type Name|\`ConsentDialogProps\`|
167
- |Source Path|\`./packages/react/src/components/consent-dialog/consent-dialog.tsx\`|
168
-
169
- \*ExtractedTypeTable: Could not extract "ConsentDialogProps" from "./packages/react/src/components/consent-dialog/consent-dialog.tsx" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
198
+ Keep `ConsentWidget` inside `Content`: it owns the draft, the switches and
199
+ the policy actions, and inherits the dialog's `uiSource`. `Footer` renders
200
+ the branding tag unless you pass children or `hideBranding`.
201
+
202
+ The positioner carries `data-slot="dialog-positioner"`, and both it and the
203
+ panel carry `data-blocking="true"` while blocking. Provider component slots
204
+ for the stock structure are `dialog.root`, `dialog.container`,
205
+ `dialog.card`, `dialog.header`, `dialog.title`, `dialog.content`,
206
+ `dialog.overlay`, `description.dialog`, `manager.footer` and `tag.dialog`.
207
+
208
+ The compound parts import from `c15t/next` as properties of `ConsentDialog`,
209
+ and `ConsentWidget` from the same module. Server Components cannot render
210
+ them: keep the composition in a file with `'use client'`. See
211
+ [Styling](../styling/overview.md) for slots and tokens.
212
+
213
+ ## Verify
214
+
215
+ Use the default blocking, uncontrolled dialog and an optional in-scope
216
+ category that is not restricted by policy or privacy signals such as GPC.
217
+ Open the dialog from the banner's Customize button or a preferences link.
218
+ A centered card appears over a dimmed backdrop, the page behind it stops
219
+ scrolling, and Tab stays inside the card. Press Escape: the card closes and
220
+ focus returns to the button you used. Open it again, turn a category on and
221
+ choose Save. The dialog closes and `useConsent('<category>')` reports
222
+ `true` in your components; under a choice prompt the banner does not return,
223
+ while a `notice` prompt keeps its banner until it is acknowledged. Reload the
224
+ page and reopen the dialog: the switch reflects the saved choice.