@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,258 +1,298 @@
1
1
  ---
2
2
  title: ConsentBanner
3
- description: A pre-built consent banner that appears when user consent is
4
- needed. Supports policy-aware layout, theming, and advanced composition when
5
- markup must change.
3
+ description: Render the pre-built ConsentBanner inside a Next.js ConsentRoot and
4
+ configure its variants, per-policy buttons and compound parts.
6
5
  group: frameworks
7
6
  ---
8
- `ConsentBanner` is a ready-to-use consent banner that appears automatically in **opt-in jurisdictions** (like GDPR) where explicit consent is required before tracking. In opt-out jurisdictions (like CCPA), the banner won't appear — users get an opt-out mechanism instead. It includes reject, accept, and customize buttons with configurable layout.
9
7
 
10
- ## Basic Usage
8
+ ## Usage
9
+
10
+ Keep the `ConsentRoot` and URL configuration from your
11
+ [App Router setup](../app-router.md). Render one banner inside
12
+ that existing root. Keep the global stylesheet from that setup,
13
+ with the c15t import after Tailwind CSS 4's import:
11
14
 
12
15
  ```tsx
13
- import { ConsentManagerProvider, ConsentBanner } from '@c15t/nextjs';
14
-
15
- export function ConsentManager() {
16
- return (
17
- <ConsentManagerProvider options={{ mode: 'hosted', backendURL: '/api/c15t' }}>
18
- <ConsentBanner />
19
- </ConsentManagerProvider>
20
- );
16
+ // app/layout.tsx
17
+ import { ConsentBanner, ConsentDialog, ConsentRoot } from 'c15t/next';
18
+ import { resolveConsent } from 'c15t/next/server';
19
+ import { consentConfig } from '../c15t.config';
20
+ import './globals.css';
21
+
22
+ export default function Layout({ children }: { children: React.ReactNode }) {
23
+ const state = resolveConsent({ config: consentConfig });
24
+
25
+ return (
26
+ <html lang="en">
27
+ <body>
28
+ <ConsentRoot state={state} config={consentConfig}>
29
+ {children}
30
+ <ConsentBanner />
31
+ <ConsentDialog />
32
+ </ConsentRoot>
33
+ </body>
34
+ </html>
35
+ );
21
36
  }
22
37
  ```
23
38
 
24
- ## Button Layout
25
-
26
- The `layout` prop controls button arrangement. Each item is either a button ID or an array of button IDs (which groups them together):
27
-
28
- ```tsx
29
- {/* Default: reject and accept grouped, customize separate */}
30
- <ConsentBanner layout={[['reject', 'accept'], 'customize']} />
31
-
32
- {/* All buttons in one group */}
33
- <ConsentBanner layout={[['reject', 'customize', 'accept']]} />
39
+ The banner renders what the resolved policy rule requires. This example lets
40
+ the page render while the prompt waits for the prefetch promise. Await the
41
+ helper inside `Suspense` to render resolved consent UI on the server; the
42
+ surrounding shell may stream first. The server and client use the same
43
+ prepared state:
44
+
45
+ * A `choice` prompt shows the actions the rule allows: reject and accept at
46
+ equal prominence, plus customize when the rule offers it.
47
+ * A `notice` prompt shows an "OK" button and a button styled as
48
+ underlined text, labeled "Do not sell or share my data". The latter opens
49
+ preferences. A notice never traps focus or locks scroll.
50
+ * A rule with `prompt: 'none'` renders nothing.
51
+
52
+ An opt-out rule with `prompt: 'none'` still has rights, so the preference
53
+ center and the dialog trigger stay available as the route to preferences. A
54
+ rule with `model: 'none'` owes no rights, so nothing renders unless you add
55
+ `rights: ['preferences']`. With no resolved rule at all, because resolution
56
+ failed, no rule matched and you set no default, or init is still withheld, no
57
+ consent surface renders: not the banner, the dialog, the widget, the
58
+ preferences link, or the trigger. They appear as soon as a rule resolves,
59
+ without a remount. `offline()` without `policyRules` resolves the recommended
60
+ pack, so it shows the strict opt-in banner until you pass a country.
61
+
62
+ Acknowledging a notice records its dismissal. It does not record consent or
63
+ change category permissions, including existing denials and privacy-signal
64
+ restrictions. Customize the label through `common.acknowledge` in your
65
+ translations, or use `dismissButtonText` for one banner.
66
+
67
+ The additional preferences button uses the opt-out label when appropriate.
68
+ A choice prompt without Customize renders a "Manage preferences" button.
69
+ Both are button elements that open the preference center; CSS gives them
70
+ an underlined text appearance. They do not submit an opt-out by themselves.
71
+
72
+ `preferenceControls` recommends these extra buttons for the stock UI.
73
+ It does not verify disclosure or access to rights. Configure your legal links
74
+ and keep preferences reachable after the banner closes.
34
75
 
35
- {/* Accept first, then reject and customize grouped */}
36
- <ConsentBanner layout={['accept', ['reject', 'customize']]} />
37
- ```
76
+ ## Props
38
77
 
39
- To stack groups vertically, pair the same grouped layout with `direction="column"`:
78
+ | Prop | Type | Default | Description |
79
+ | --------------------- | --------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------- |
80
+ | `title` | `ReactNode` | translation | Overrides the title. Under a notice the default is `cookieBanner.noticeTitle`. |
81
+ | `description` | `ReactNode` | translation | Overrides the description. Under a notice the default is `cookieBanner.noticeDescription`. |
82
+ | `acceptButtonText` | `ReactNode` | `common.acceptAll` | Accept label. |
83
+ | `rejectButtonText` | `ReactNode` | `common.rejectAll` | Reject label. |
84
+ | `customizeButtonText` | `ReactNode` | `common.customize` | Customize label. |
85
+ | `dismissButtonText` | `ReactNode` | `common.acknowledge` | Label of the notice acknowledgement. |
86
+ | `variant` | `PromptVariant` | `floating` | Shape of the prompt. See [Variants](#variants). |
87
+ | `position` | `PromptPosition` | per variant | Where the prompt sits. Must be valid for the variant. |
88
+ | `blocking` | `boolean` | `true` on `wall` | Backdrop, scroll lock, focus trap, and no outside dismissal, as one value. |
89
+ | `layout` | `ConsentBannerLayout` | policy default | Orders and groups actions. Required actions the layout omits are restored. |
90
+ | `primaryButton` | `ConsentBannerButton \|ConsentBannerButton[]` | `'customize'` | Which actions get the primary treatment. On a notice, dismiss is primary when it is the only action. |
91
+ | `direction` | `'row' \|'column'` | `'row'` | How action groups flow. |
92
+ | `legalLinks` | `(keyof LegalLinks)[] \|null` | none | Which configured legal links render inline. |
93
+ | `hideBranding` | `boolean` | `false` | Hides the "Secured by" tag. |
94
+ | `scrollLock` | `boolean` | unset | Deprecated. Use `blocking` to control scrolling, focus and backdrop together. |
95
+ | `trapFocus` | `boolean` | unset | Deprecated. Use `blocking`. |
96
+ | `disableAnimation` | `boolean` | `false` | Skips enter and exit animations. |
97
+ | `noStyle` | `boolean` | `false` | Removes the built-in styling from every part. |
98
+
99
+ ## Per-policy buttons
100
+
101
+ The default already makes Customize primary on a choice banner and OK
102
+ primary on a notice. To set button order and treatment for specific rules,
103
+ read the active policy inside the provider:
40
104
 
41
105
  ```tsx
42
- <ConsentBanner
43
- layout={['customize', ['reject', 'accept']]}
44
- direction="column"
45
- />
106
+ 'use client';
107
+
108
+ import { ConsentBanner, usePolicyRule } from 'c15t/next';
109
+ import type { ConsentBannerProps } from 'c15t/next';
110
+
111
+ const banners: Record<
112
+ string,
113
+ Pick<ConsentBannerProps, 'layout' | 'primaryButton' | 'variant'>
114
+ > = {
115
+ europe_opt_in: {
116
+ layout: [['reject', 'accept'], 'customize'],
117
+ primaryButton: 'customize',
118
+ },
119
+ us_privacy_states: {
120
+ layout: ['dismiss'],
121
+ primaryButton: 'dismiss',
122
+ variant: 'bar',
123
+ },
124
+ };
125
+
126
+ export function RegionalBanner() {
127
+ const policy = usePolicyRule();
128
+ return <ConsentBanner {...(policy ? banners[policy.id] : undefined)} />;
129
+ }
46
130
  ```
47
131
 
48
- ## Policy-Driven UI Profile
49
-
50
- When using backend runtime policies, `policy.ui.uiProfile` can control banner action presentation:
51
-
52
- * `compact` — default desktop sizing
53
- * `balanced` — auto-fills compact and grouped layouts with moderate emphasis
54
- * `strict` — always fills action controls for explicit, high-clarity layouts
55
-
56
- ## Theme-Level Button Styling
57
-
58
- Use the provider `theme` prop to control how stock consent actions look:
59
-
60
- ```tsx
61
- <ConsentManagerProvider
62
- options={{
63
- theme: {
64
- consentActions: {
65
- default: { mode: 'stroke' },
66
- accept: { variant: 'primary', mode: 'stroke' },
67
- customize: { variant: 'neutral', mode: 'ghost' },
68
- },
69
- },
70
- }}
71
- >
72
- <ConsentBanner />
73
- </ConsentManagerProvider>
132
+ Render `RegionalBanner` inside your existing consent boundary, replacing its
133
+ stock banner. The layout controls the
134
+ action groups; the opt-out preferences button still appears on a notice.
135
+ Required actions omitted from a layout are restored. Accept and Reject
136
+ keep equivalent default prominence.
137
+
138
+ Choose one brand color and make whichever action is primary use a filled
139
+ button through the provider's theme:
140
+
141
+ ```ts
142
+ const theme = {
143
+ colors: { primary: '#2f6f4e', primaryHover: '#24563c' },
144
+ consentActions: {
145
+ primary: { variant: 'primary', mode: 'filled' },
146
+ },
147
+ } as const;
74
148
  ```
75
149
 
76
- Policy packs control grouping, ordering, and direction. The theme controls button appearance.
77
-
78
- ## Styling First
150
+ Pass `theme` in `ConsentProvider` options. Per-action theme overrides such
151
+ as `consentActions.dismiss` take precedence over this primary style.
152
+ See [Styling](../styling/overview.md) for tokens and slots, and
153
+ [Policy presets](../concepts/policy-presets.md) for the rule IDs and coverage.
79
154
 
80
- > ℹ️ **Info:**
81
- > For pure theming, stay inside the pre-built banner. Start with layout props, theme.consentActions, design tokens, and theme.slots before reaching for compound components. See Styling Overview.
155
+ ## Variants
82
156
 
83
- The stock banner maps common visual changes to the theme system:
157
+ The policy decides which actions the banner offers. The variant decides the
158
+ shape those actions take. Both come from the same component, so a bar for a
159
+ notice region and a card for an opt-in region need no extra components.
84
160
 
85
- * Card background -> `theme.colors.surface`
86
- * Footer background -> `theme.colors.surfaceHover`
87
- * Card, footer, and title tweaks -> `theme.slots.consentBannerCard`, `consentBannerFooter`, and `consentBannerTitle`
161
+ Set the variant on the banner, or on the provider under
162
+ `presentation.prompt` when every banner should share it. The prop wins.
88
163
 
89
164
  ```tsx
90
- <ConsentManagerProvider
91
- options={{
92
- theme: {
93
- colors: {
94
- surface: '#fffdf8',
95
- surfaceHover: '#f6f3ee',
96
- },
97
- slots: {
98
- consentBannerCard: 'rounded-[28px] shadow-xl',
99
- consentBannerFooter: 'border-t border-black/10 px-6',
100
- consentBannerTitle: 'tracking-tight',
101
- },
102
- },
103
- }}
104
- >
105
- <ConsentBanner />
106
- </ConsentManagerProvider>
165
+ <ConsentBanner variant="floating" position="bottom-center" />
107
166
  ```
108
167
 
109
- ### Primary Button
110
-
111
- Highlight specific button(s) as the primary action:
168
+ A floating card in a corner or centered on an edge. This is the default for
169
+ every prompt. A notice keeps the same card, with its right link and "OK" in the footer.
112
170
 
113
171
  ```tsx
114
- {/* Single primary */}
115
- <ConsentBanner primaryButton="accept" />
116
-
117
- {/* Multiple primaries */}
118
- <ConsentBanner primaryButton={['accept', 'customize']} />
172
+ <ConsentBanner variant="bar" position="top" />
119
173
  ```
120
174
 
121
- ## Legal Links
122
-
123
- Control which legal links appear in the banner description:
175
+ A bar across the full width of the viewport. From 1024px wide the text, the
176
+ right links, and the controls share one row. Opt in to it for regions that
177
+ expect a classic cookie bar.
124
178
 
125
179
  ```tsx
126
- {/* Show all configured links (default) */}
127
- <ConsentBanner legalLinks={undefined} />
128
-
129
- {/* Show no links */}
130
- <ConsentBanner legalLinks={null} />
131
-
132
- {/* Show specific links */}
133
- <ConsentBanner legalLinks={['privacyPolicy', 'cookiePolicy']} />
134
- ```
135
-
136
- > ℹ️ **Info:**
137
- > Legal link URLs are configured in the ConsentManagerProvider options via the legalLinks prop, not on the banner itself.
138
-
139
- ## Customizing Copy
140
-
141
- Prefer provider `i18n` when you want to rename the stock banner content:
142
-
143
- ```tsx
144
- <ConsentManagerProvider
145
- options={{
146
- i18n: {
147
- locale: 'en',
148
- messages: {
149
- en: {
150
- cookieBanner: {
151
- title: 'We value your privacy',
152
- description: 'We use cookies to improve the site and measure performance.',
153
- },
154
- common: {
155
- acceptAll: 'Accept all',
156
- rejectAll: 'Reject all',
157
- customize: 'Manage preferences',
158
- },
159
- },
160
- },
161
- },
162
- }}
163
- >
164
- <ConsentBanner />
165
- </ConsentManagerProvider>
166
- ```
167
-
168
- Direct text props such as `title`, `description`, and `acceptButtonText` are still supported for one-off overrides, but `i18n` is the preferred path for copy changes.
169
-
170
- ## Advanced: Compound Components
171
-
172
- Use compound components only when the stock banner structure is no longer enough and you need to rearrange existing c15t primitives while keeping policy-driven action grouping and emphasis:
173
-
174
- ```tsx
175
- <ConsentBanner.Root>
176
- <ConsentBanner.Overlay />
177
- <ConsentBanner.Card>
178
- <ConsentBanner.Header>
179
- <ConsentBanner.Title />
180
- <ConsentBanner.Description />
181
- </ConsentBanner.Header>
182
- <ConsentBanner.PolicyActions />
183
- </ConsentBanner.Card>
184
- </ConsentBanner.Root>
180
+ <ConsentBanner variant="widget" position="bottom-right" />
185
181
  ```
186
182
 
187
- * `ConsentBanner.Root` — Outermost container, provides theme context
188
- * `ConsentBanner.Card` — Main content card with optional focus trapping
189
- * `ConsentBanner.Header` — Contains title and description
190
- * `ConsentBanner.Title` — Heading, defaults to translation `consentBanner.title`
191
- * `ConsentBanner.Description` — Description text, supports `legalLinks` prop
192
- * `ConsentBanner.PolicyActions` — Renders policy-aware grouped actions inside the banner footer
193
- * `ConsentBanner.Footer` — Action buttons container
194
- * `ConsentBanner.FooterSubGroup` — Groups related buttons together
195
- * `ConsentBanner.RejectButton` — Rejects all consent
196
- * `ConsentBanner.CustomizeButton` — Opens the consent dialog
197
- * `ConsentBanner.AcceptButton` — Accepts all consent
198
- * `ConsentBanner.Overlay` — Optional backdrop overlay
199
-
200
- For a fixed layout that intentionally ignores policy grouping, render the footer manually:
183
+ A compact card with smaller type. The full description and its legal links
184
+ remain visible. Pair it with a short notice.
201
185
 
202
186
  ```tsx
203
- <ConsentBanner.Root>
204
- <ConsentBanner.Card>
205
- <ConsentBanner.Header>
206
- <ConsentBanner.Title />
207
- <ConsentBanner.Description />
208
- </ConsentBanner.Header>
209
- <ConsentBanner.Footer>
210
- <ConsentBanner.FooterSubGroup>
211
- <ConsentBanner.RejectButton />
212
- <ConsentBanner.AcceptButton />
213
- </ConsentBanner.FooterSubGroup>
214
- <ConsentBanner.CustomizeButton />
215
- </ConsentBanner.Footer>
216
- </ConsentBanner.Card>
217
- </ConsentBanner.Root>
187
+ <ConsentBanner variant="wall" />
218
188
  ```
219
189
 
220
- ## Using `renderAction` with c15t Defaults
221
-
222
- `ConsentBanner.PolicyActions` renders stock c15t buttons and translations by default.
223
-
224
- ```tsx
225
- <ConsentBanner.PolicyActions />
190
+ A centered card over a backdrop that blocks the page until the visitor
191
+ answers. A wall is always blocking.
192
+
193
+ Each variant accepts its own positions:
194
+
195
+ | Variant | Positions | Default |
196
+ | ---------- | ------------------------------------------------------------------------------------- | -------------- |
197
+ | `floating` | `bottom-left`, `bottom-right`, `top-left`, `top-right`, `bottom-center`, `top-center` | `bottom-left` |
198
+ | `bar` | `top`, `bottom` | `bottom` |
199
+ | `widget` | `bottom-left`, `bottom-right`, `top-left`, `top-right` | `bottom-right` |
200
+ | `wall` | `center` | `center` |
201
+
202
+ A default corner mirrors left and right for right-to-left languages. A
203
+ position you set is never mirrored. A position that is not valid for the
204
+ variant falls back to the default and logs an `invalid-position` diagnostic
205
+ in development.
206
+
207
+ `blocking` controls the backdrop, scroll lock, and focus trap together.
208
+ An explicit value overrides the deprecated `scrollLock` and `trapFocus`
209
+ options. Without `blocking`, either legacy option set to `false` selects
210
+ non-blocking behavior; otherwise a legacy `true` selects blocking behavior.
211
+
212
+ A choice `wall` always blocks. Notices always stay non-blocking, and asking
213
+ for a notice `wall` falls back to `floating` with an `invalid-variant`
214
+ diagnostic. Blocking banners carry `role="dialog"` and `aria-modal="true"`.
215
+ Non-blocking banners leave page controls usable by keyboard and pointer.
216
+
217
+ `PromptVariant` and `PromptPosition` are exported from `c15t/next`.
218
+ Compound parts can read the resolved shape with `useConsentBannerSurface()`,
219
+ which returns `variant`, `position`, `positionSource` (`host` or `default`),
220
+ and `blocking`.
221
+
222
+ ## Composition
223
+
224
+ Every part is available as `ConsentBanner.<Part>` for custom layouts. The
225
+ parts read the same policy state the pre-built banner does, so a custom layout
226
+ still gets the right actions for the active rule. Render this Client Component
227
+ inside the existing boundary, replacing its stock banner. Keep the dialog and
228
+ persistent preferences control.
229
+
230
+ ```tsx title="components/compact-banner.tsx"
231
+ 'use client';
232
+
233
+ import { ConsentBanner } from 'c15t/next';
234
+
235
+ export function CompactBanner() {
236
+ return (
237
+ <ConsentBanner.Root>
238
+ <ConsentBanner.Card>
239
+ <ConsentBanner.Header>
240
+ <ConsentBanner.Title />
241
+ <ConsentBanner.Description />
242
+ </ConsentBanner.Header>
243
+ <ConsentBanner.PolicyActions />
244
+ </ConsentBanner.Card>
245
+ </ConsentBanner.Root>
246
+ );
247
+ }
226
248
  ```
227
249
 
228
- `renderAction` is optional. When you want custom mapping but still want the built-in c15t button behavior and copy, return the stock button compounds:
250
+ `ConsentBanner.PolicyActions` renders the resolved action groups and, before
251
+ them, the additional preferences buttons. Pass children to replace those
252
+ buttons while retaining the policy action groups. This supports custom labels
253
+ and button markup.
254
+
255
+ Use the individual parts when you need a different order or your own markup:
256
+
257
+ * `ConsentBanner.AcceptButton`, `ConsentBanner.RejectButton`,
258
+ `ConsentBanner.CustomizeButton`, and `ConsentBanner.DismissButton` render one
259
+ action each. `DismissButton` defaults its label to `common.acknowledge`.
260
+ * `ConsentBanner.Rights` renders the additional preferences buttons. It
261
+ renders nothing when the list is empty. Pass `rights` to override the
262
+ list.
263
+ * `ConsentBanner.RightLink` renders a single right. `right` is `'opt-out'`
264
+ or `'preferences'`. By default it is a button element styled as an
265
+ underlined text link, carrying `data-action="right"` and `data-right`; it
266
+ opens the preference center on click and accepts `asChild` to render your
267
+ own element, such as an anchor to a dedicated opt-out page.
229
268
 
230
269
  ```tsx
231
- <ConsentBanner.PolicyActions
232
- renderAction={(action, props) => {
233
- const { key, ...buttonProps } = props
234
-
235
- switch (action) {
236
- case 'accept':
237
- return <ConsentBanner.AcceptButton key={key} {...buttonProps} />
238
- case 'reject':
239
- return <ConsentBanner.RejectButton key={key} {...buttonProps} />
240
- case 'customize':
241
- return <ConsentBanner.CustomizeButton key={key} {...buttonProps} />
242
- }
243
- }}
244
- />
270
+ <ConsentBanner.Rights>
271
+ <ConsentBanner.RightLink right="opt-out" asChild>
272
+ <a href="/privacy/do-not-sell">Do not sell or share my data</a>
273
+ </ConsentBanner.RightLink>
274
+ </ConsentBanner.Rights>
245
275
  ```
246
276
 
247
- `renderAction` is still meant for stock button compounds. If you want completely custom button elements and click handling, use `useHeadlessConsentUI()` and render `banner.actionGroups` manually instead of `ConsentBanner.PolicyActions`.
277
+ `useBannerCopy()` returns the title, description, and prompt kind the banner
278
+ would use, for custom headers that still follow the notice copy.
248
279
 
249
- If you only need styling changes, stay with tokens and slots instead of rebuilding the banner layout.
280
+ ## Data attributes
250
281
 
251
- ## Props
282
+ The root element carries attributes you can target from CSS or Tailwind. The
283
+ card carries `data-state` (`open` or `closed`) for the enter and exit
284
+ animations.
252
285
 
253
- |Property|Value|
254
- |:--|:--|
255
- |Type Name|\`ConsentBannerProps\`|
256
- |Source Path|\`./packages/react/src/components/consent-banner/consent-banner.tsx\`|
286
+ | Attribute | Values |
287
+ | --------------- | ------------------------------------- |
288
+ | `data-prompt` | `choice`, `notice` |
289
+ | `data-model` | `opt-in`, `opt-out`, `iab` |
290
+ | `data-variant` | `floating`, `bar`, `widget`, `wall` |
291
+ | `data-position` | The resolved position for the variant |
292
+ | `data-blocking` | `true`, present only while blocking |
257
293
 
258
- \*ExtractedTypeTable: Could not extract "ConsentBannerProps" from "./packages/react/src/components/consent-banner/consent-banner.tsx" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
294
+ Each action button carries `data-action`, and each right link carries
295
+ `data-action="right"` plus `data-right`. The built-in stylesheet keys every
296
+ variant's geometry on `data-variant` and `data-position`, and uses
297
+ `data-prompt="notice"` to lay the footer out as one row with the right
298
+ links leading and "OK" trailing.