@c15t/nextjs 2.0.0-rc.5 → 2.0.0-rc.7
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.
- package/dist/headless.cjs +1 -1
- package/dist/iab/styles.css +1 -0
- package/dist/iab/styles.tw3.css +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/libs/browser-initial-data.cjs +1 -1
- package/dist/libs/browser-initial-data.js +1 -1
- package/dist/libs/initial-data.cjs +1 -1
- package/dist/styles.css +1 -0
- package/dist/styles.tw3.css +1 -0
- package/dist/types.cjs +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist-types/headless.d.ts +1 -1
- package/dist-types/version.d.ts +1 -1
- package/docs/building-headless-components.md +7 -4
- package/docs/components/consent-banner.md +68 -18
- package/docs/components/consent-dialog.md +31 -2
- package/docs/components/consent-manager-provider.md +1 -0
- package/docs/components/consent-widget.md +31 -2
- package/docs/concepts/policy-packs.md +1 -1
- package/docs/headless.md +13 -7
- package/docs/hooks/use-consent-manager/overview.md +2 -2
- package/docs/hooks/use-translations.md +1 -0
- package/docs/iab/consent-banner.md +1 -1
- package/docs/quickstart.md +13 -3
- package/docs/styling/classnames.md +17 -9
- package/docs/styling/overview.md +166 -29
- package/docs/styling/slots.md +37 -7
- package/docs/styling/tailwind.md +30 -9
- package/package.json +17 -8
- package/src/iab/styles.css +10 -0
- package/src/iab/styles.tw3.css +10 -0
- package/src/styles.css +10 -0
- package/src/styles.tw3.css +10 -0
package/dist/headless.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_modules__={"@c15t/react/headless"(e){e.exports=require("@c15t/react/headless")}},__webpack_module_cache__={};function __webpack_require__(e){var _=__webpack_module_cache__[e];if(void 0!==_)return _.exports;var r=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e](r,r.exports,__webpack_require__),r.exports}__webpack_require__.n=e=>{var _=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(_,{a:_}),_},__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};for(var __rspack_i in(()=>{__webpack_require__.r(__webpack_exports__);var e=__webpack_require__("@c15t/react/headless"),_={};for(let r in e)"default"!==r&&(_[r]=()=>e[r]);__webpack_require__.d(__webpack_exports__,_)})(),__webpack_exports__)exports[__rspack_i]=__webpack_exports__[__rspack_i];Object.defineProperty(exports,"__esModule",{value:!0});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@c15t/react/iab/styles.css";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@c15t/react/iab/styles.tw3.css";
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_modules__={"./libs/browser-initial-data"(e){e.exports=require("./libs/browser-initial-data.cjs")},"./libs/initial-data"(e){e.exports=require("./libs/initial-data.cjs")},"@c15t/react"(e){e.exports=require("@c15t/react")},c15t(e){e.exports=require("c15t")}},__webpack_module_cache__={};function __webpack_require__(e){var _=__webpack_module_cache__[e];if(void 0!==_)return _.exports;var t=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e](t,t.exports,__webpack_require__),t.exports}__webpack_require__.n=e=>{var _=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(_,{a:_}),_},__webpack_require__.d=(e,_)=>{for(var t in _)__webpack_require__.o(_,t)&&!__webpack_require__.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:_[t]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};for(var __rspack_i in(()=>{__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{C15tPrefetch:()=>r.C15tPrefetch,buildPrefetchScript:()=>t.buildPrefetchScript,ensurePrefetchedInitialData:()=>t.ensurePrefetchedInitialData,fetchInitialData:()=>a.fetchInitialData,getPrefetchedInitialData:()=>t.getPrefetchedInitialData});var e=__webpack_require__("@c15t/react"),_={};for(let t in e)0>["C15tPrefetch","fetchInitialData","default","buildPrefetchScript","ensurePrefetchedInitialData","getPrefetchedInitialData"].indexOf(t)&&(_[t]=()=>e[t]);__webpack_require__.d(__webpack_exports__,_);var t=__webpack_require__("c15t"),r=__webpack_require__("./libs/browser-initial-data"),a=__webpack_require__("./libs/initial-data")})(),exports.C15tPrefetch=__webpack_exports__.C15tPrefetch,exports.buildPrefetchScript=__webpack_exports__.buildPrefetchScript,exports.ensurePrefetchedInitialData=__webpack_exports__.ensurePrefetchedInitialData,exports.fetchInitialData=__webpack_exports__.fetchInitialData,exports.getPrefetchedInitialData=__webpack_exports__.getPrefetchedInitialData,__webpack_exports__)-1===["C15tPrefetch","buildPrefetchScript","ensurePrefetchedInitialData","fetchInitialData","getPrefetchedInitialData"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export*from"@c15t/react";export{buildPrefetchScript,ensurePrefetchedInitialData,getPrefetchedInitialData}from"c15t";export{C15tPrefetch}from"./libs/browser-initial-data.js";export{fetchInitialData}from"./libs/initial-data.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.n=e=>{var _=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(_,{a:_}),_},__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{C15tPrefetch:()=>C15tPrefetch});const jsx_runtime_namespaceObject=require("react/jsx-runtime"),external_c15t_namespaceObject=require("c15t"),script_namespaceObject=require("next/script");var script_default=__webpack_require__.n(script_namespaceObject);const DEFAULT_SCRIPT_ID="c15t-initial-data-prefetch";function C15tPrefetch({id:e="c15t-initial-data-prefetch",..._}){return(0,jsx_runtime_namespaceObject.jsx)(script_default(),{id:e,strategy:"beforeInteractive",children:(0,external_c15t_namespaceObject.buildPrefetchScript)(_)})}for(var __rspack_i in exports.C15tPrefetch=__webpack_exports__.C15tPrefetch,__webpack_exports__)-1===["C15tPrefetch"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as t}from"react/jsx-runtime";import{buildPrefetchScript as r}from"c15t";import e from"next/script";function i({id:
|
|
1
|
+
import{jsx as t}from"react/jsx-runtime";import{buildPrefetchScript as r}from"c15t";import e from"next/script";function i({id:c="c15t-initial-data-prefetch",...o}){return t(e,{id:c,strategy:"beforeInteractive",children:r(o)})}export{i as C15tPrefetch};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{fetchInitialData:()=>fetchInitialData});const server_namespaceObject=require("@c15t/react/server"),cache_namespaceObject=require("next/cache"),headers_namespaceObject=require("next/headers"),DEFAULT_REVALIDATE_SECONDS=1;async function fetchInitialData(e){let _=await (0,headers_namespaceObject.headers)(),r=(0,server_namespaceObject.normalizeBackendURL)(e.backendURL,_);if(!r)return;let a=e.nextCache?.revalidateSeconds;if(!1===a)return(0,server_namespaceObject.fetchSSRData)({...e,backendURL:r,headers:_});let t=(0,server_namespaceObject.createSSRInitCacheKey)({normalizedURL:r,headers:_,overrides:e.overrides});return(0,cache_namespaceObject.unstable_cache)(()=>(0,server_namespaceObject.fetchSSRData)({...e,backendURL:r,headers:_}),["c15t:nextjs:fetchInitialData",t],{revalidate:a??1})()}for(var __rspack_i in exports.fetchInitialData=__webpack_exports__.fetchInitialData,__webpack_exports__)-1===["fetchInitialData"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@c15t/react/styles.css";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@c15t/react/styles.tw3.css";
|
package/dist/types.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};for(var __rspack_i in __webpack_require__.r(__webpack_exports__),__webpack_exports__)exports[__rspack_i]=__webpack_exports__[__rspack_i];Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/version.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{version:()=>version});const version="2.0.0-rc.7";for(var __rspack_i in exports.version=__webpack_exports__.version,__webpack_exports__)-1===["version"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e="2.0.0-rc.
|
|
1
|
+
let e="2.0.0-rc.7";export{e as version};
|
package/dist-types/headless.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from '../../react/dist-types/headless
|
|
1
|
+
export * from '../../react/dist-types/headless';
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.0-rc.
|
|
1
|
+
export declare const version = "2.0.0-rc.7";
|
|
@@ -4,9 +4,12 @@ description: Build policy-aware custom consent components in Next.js using the h
|
|
|
4
4
|
---
|
|
5
5
|
Building headless components is easier now because c15t exposes policy-aware primitives instead of forcing you to reconstruct banner rules by hand.
|
|
6
6
|
|
|
7
|
+
> ⚠️ **Warning:**
|
|
8
|
+
> Headless is the last step in the customization ladder. Use this guide only when pre-built components, tokens, slots, compound components, and noStyle are no longer sufficient.
|
|
9
|
+
|
|
7
10
|
The headless stack is:
|
|
8
11
|
|
|
9
|
-
* `useHeadlessConsentUI()` for policy-aware banner/dialog actions, ordering, layout, and primary
|
|
12
|
+
* `useHeadlessConsentUI()` for policy-aware banner/dialog actions, ordering, layout, and primary actions hints
|
|
10
13
|
* `useConsentManager()` for runtime state, categories, selected consent state, and policy metadata
|
|
11
14
|
* `useTranslations()` for the resolved copy
|
|
12
15
|
* `offlinePolicy.policyPacks` for offline previews that behave like backend policy resolution
|
|
@@ -24,7 +27,7 @@ The main win is that your custom UI can stay aligned with policy packs without d
|
|
|
24
27
|
* the order those actions should render in
|
|
25
28
|
* grouped actions from policy `layout`
|
|
26
29
|
* layout `direction` (`row` or `column`)
|
|
27
|
-
* the primary
|
|
30
|
+
* the primary actions
|
|
28
31
|
* UI profile and scroll-lock hints
|
|
29
32
|
* whether the banner or dialog should currently be visible
|
|
30
33
|
|
|
@@ -91,7 +94,7 @@ export function CustomConsentBanner() {
|
|
|
91
94
|
<button
|
|
92
95
|
key={action}
|
|
93
96
|
type="button"
|
|
94
|
-
className={
|
|
97
|
+
className={banner.primaryActions.includes(action) ? 'btn-primary' : 'btn-secondary'}
|
|
95
98
|
onClick={() => {
|
|
96
99
|
if (action === 'customize') {
|
|
97
100
|
openDialog();
|
|
@@ -202,7 +205,7 @@ When you build custom banner or dialog components, make sure they use:
|
|
|
202
205
|
|
|
203
206
|
* `activeUI` or `banner.isVisible` / `dialog.isVisible` for visibility
|
|
204
207
|
* `allowedActions`, `orderedActions`, or `actionGroups` instead of hard-coding buttons
|
|
205
|
-
* `
|
|
208
|
+
* `primaryActions` for visual emphasis
|
|
206
209
|
* `consentCategories` when deciding which category toggles to render
|
|
207
210
|
* `policyDecision` when you want to debug why a specific UI state was chosen
|
|
208
211
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: ConsentBanner
|
|
3
|
-
description: A pre-built consent banner that appears when user consent is needed. Supports
|
|
3
|
+
description: A pre-built consent banner that appears when user consent is needed. Supports policy-aware layout, theming, and advanced composition when markup must change.
|
|
4
4
|
---
|
|
5
5
|
`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.
|
|
6
6
|
|
|
@@ -18,20 +18,6 @@ export function ConsentManager() {
|
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
## Customizing Content
|
|
22
|
-
|
|
23
|
-
Override the default title, description, and button labels:
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
<ConsentBanner
|
|
27
|
-
title="We value your privacy"
|
|
28
|
-
description="We use cookies to enhance your browsing experience and analyze site traffic."
|
|
29
|
-
acceptButtonText="Accept all"
|
|
30
|
-
rejectButtonText="Reject all"
|
|
31
|
-
customizeButtonText="Manage preferences"
|
|
32
|
-
/>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
21
|
## Button Layout
|
|
36
22
|
|
|
37
23
|
The `layout` prop controls button arrangement. Each item is either a button ID or an array of button IDs (which groups them together):
|
|
@@ -86,6 +72,37 @@ Use the provider `theme` prop to control how stock consent actions look:
|
|
|
86
72
|
|
|
87
73
|
Policy packs control grouping, ordering, and direction. The theme controls button appearance.
|
|
88
74
|
|
|
75
|
+
## Styling First
|
|
76
|
+
|
|
77
|
+
> ℹ️ **Info:**
|
|
78
|
+
> 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.
|
|
79
|
+
|
|
80
|
+
The stock banner maps common visual changes to the theme system:
|
|
81
|
+
|
|
82
|
+
* Card background -> `theme.colors.surface`
|
|
83
|
+
* Footer background -> `theme.colors.surfaceHover`
|
|
84
|
+
* Card, footer, and title tweaks -> `theme.slots.consentBannerCard`, `consentBannerFooter`, and `consentBannerTitle`
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<ConsentManagerProvider
|
|
88
|
+
options={{
|
|
89
|
+
theme: {
|
|
90
|
+
colors: {
|
|
91
|
+
surface: '#fffdf8',
|
|
92
|
+
surfaceHover: '#f6f3ee',
|
|
93
|
+
},
|
|
94
|
+
slots: {
|
|
95
|
+
consentBannerCard: 'rounded-[28px] shadow-xl',
|
|
96
|
+
consentBannerFooter: 'border-t border-black/10 px-6',
|
|
97
|
+
consentBannerTitle: 'tracking-tight',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<ConsentBanner />
|
|
103
|
+
</ConsentManagerProvider>
|
|
104
|
+
```
|
|
105
|
+
|
|
89
106
|
### Primary Button
|
|
90
107
|
|
|
91
108
|
Highlight specific button(s) as the primary action:
|
|
@@ -116,9 +133,40 @@ Control which legal links appear in the banner description:
|
|
|
116
133
|
> ℹ️ **Info:**
|
|
117
134
|
> Legal link URLs are configured in the ConsentManagerProvider options via the legalLinks prop, not on the banner itself.
|
|
118
135
|
|
|
119
|
-
##
|
|
136
|
+
## Customizing Copy
|
|
137
|
+
|
|
138
|
+
Prefer provider `i18n` when you want to rename the stock banner content:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<ConsentManagerProvider
|
|
142
|
+
options={{
|
|
143
|
+
i18n: {
|
|
144
|
+
locale: 'en',
|
|
145
|
+
messages: {
|
|
146
|
+
en: {
|
|
147
|
+
cookieBanner: {
|
|
148
|
+
title: 'We value your privacy',
|
|
149
|
+
description: 'We use cookies to improve the site and measure performance.',
|
|
150
|
+
},
|
|
151
|
+
common: {
|
|
152
|
+
acceptAll: 'Accept all',
|
|
153
|
+
rejectAll: 'Reject all',
|
|
154
|
+
customize: 'Manage preferences',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<ConsentBanner />
|
|
162
|
+
</ConsentManagerProvider>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
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.
|
|
120
166
|
|
|
121
|
-
|
|
167
|
+
## Advanced: Compound Components
|
|
168
|
+
|
|
169
|
+
Use compound components only when the stock banner structure is no longer enough and you need to rearrange existing c15t primitives:
|
|
122
170
|
|
|
123
171
|
```tsx
|
|
124
172
|
<ConsentBanner.Root>
|
|
@@ -142,7 +190,7 @@ Build fully custom banner layouts using sub-components:
|
|
|
142
190
|
* `ConsentBanner.Root` — Outermost container, provides theme context
|
|
143
191
|
* `ConsentBanner.Card` — Main content card with optional focus trapping
|
|
144
192
|
* `ConsentBanner.Header` — Contains title and description
|
|
145
|
-
* `ConsentBanner.Title` — Heading, defaults to translation `
|
|
193
|
+
* `ConsentBanner.Title` — Heading, defaults to translation `cookieBanner.title`
|
|
146
194
|
* `ConsentBanner.Description` — Description text, supports `legalLinks` prop
|
|
147
195
|
* `ConsentBanner.Footer` — Action buttons container
|
|
148
196
|
* `ConsentBanner.FooterSubGroup` — Groups related buttons together
|
|
@@ -151,6 +199,8 @@ Build fully custom banner layouts using sub-components:
|
|
|
151
199
|
* `ConsentBanner.AcceptButton` — Accepts all consent
|
|
152
200
|
* `ConsentBanner.Overlay` — Optional backdrop overlay
|
|
153
201
|
|
|
202
|
+
If you only need styling changes, stay with tokens and slots instead of rebuilding the banner layout.
|
|
203
|
+
|
|
154
204
|
## Props
|
|
155
205
|
|
|
156
206
|
### ConsentBannerProps
|
|
@@ -81,9 +81,36 @@ Hide the c15t branding in the dialog footer:
|
|
|
81
81
|
<ConsentDialog hideBranding />
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
##
|
|
84
|
+
## Styling First
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
> ℹ️ **Info:**
|
|
87
|
+
> 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, consentDialogHeader, and consentDialogFooter. See Styling Overview.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<ConsentManagerProvider
|
|
91
|
+
options={{
|
|
92
|
+
theme: {
|
|
93
|
+
colors: {
|
|
94
|
+
surface: '#fffdf8',
|
|
95
|
+
surfaceHover: '#f6f3ee',
|
|
96
|
+
},
|
|
97
|
+
slots: {
|
|
98
|
+
consentDialogCard: 'rounded-[32px] shadow-xl',
|
|
99
|
+
consentDialogHeader: 'gap-3',
|
|
100
|
+
consentDialogFooter: 'border-t border-black/10 px-6',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<ConsentDialog />
|
|
106
|
+
</ConsentManagerProvider>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Dialog copy should be changed through `ConsentManagerProvider.options.i18n`, not by rebuilding the dialog structure.
|
|
110
|
+
|
|
111
|
+
## Advanced: Compound Components
|
|
112
|
+
|
|
113
|
+
Use compound components only when you need custom dialog markup while still keeping c15t primitives:
|
|
87
114
|
|
|
88
115
|
```tsx
|
|
89
116
|
<ConsentDialog.Root>
|
|
@@ -118,6 +145,8 @@ For a quick pre-composed layout, use the shorthand card:
|
|
|
118
145
|
</ConsentDialog.Root>
|
|
119
146
|
```
|
|
120
147
|
|
|
148
|
+
If the stock dialog structure still works, prefer tokens, slots, and provider configuration instead.
|
|
149
|
+
|
|
121
150
|
## Props
|
|
122
151
|
|
|
123
152
|
### ConsentDialogProps
|
|
@@ -146,6 +146,7 @@ IAB TCF 2.3 configuration.
|
|
|
146
146
|
|Property|Type|Description|Default|Required|
|
|
147
147
|
|:--|:--|:--|:--|:--:|
|
|
148
148
|
|enabled|boolean|Enable IAB TCF 2.3 mode. Note: Only works in 'hosted' client mode (legacy alias: 'c15t') because it requires a backend. Options: Fetch GVL from gvl.consent.io; Initialize \_\_tcfapi CMP API; Generate TC Strings for IAB compliance|-|✅ Required|
|
|
149
|
+
|\_module|IABModule \|undefined|IAB runtime module injected by \`@c15t/iab\`.|-|Optional|
|
|
149
150
|
|cmpId|number \|undefined|CMP ID registered with IAB Europe. When using consent.io as the backend, this is automatically provided via the \`/init\` endpoint — no client-side configuration needed. Only set this if you self-host and have your own CMP registration. A valid (non-zero) CMP ID is required for IAB TCF compliance.|-|Optional|
|
|
150
151
|
|cmpVersion|string \|number \|undefined|CMP version. When omitted, defaults to package version from \`\~/cmp-defaults\` (which uses \~/version).|-|Optional|
|
|
151
152
|
|vendors|number\[] \|undefined|IAB-registered vendor IDs to include (optional). Used to scope the vendor list when fetching GVL or when hosted fallback paths are used (e.g. if GVL fetch fails).|-|Optional|
|
|
@@ -35,9 +35,36 @@ export function PrivacySettingsPage() {
|
|
|
35
35
|
|
|
36
36
|
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.
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Styling First
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
> ℹ️ **Info:**
|
|
41
|
+
> Most widget customization should stay in the stock component. Use theme tokens and slots such as consentWidgetAccordion, consentWidgetFooter, and toggle before reaching for compound components. See Styling Overview.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<ConsentManagerProvider
|
|
45
|
+
options={{
|
|
46
|
+
theme: {
|
|
47
|
+
colors: {
|
|
48
|
+
surface: '#fffdf8',
|
|
49
|
+
surfaceHover: '#f6f3ee',
|
|
50
|
+
},
|
|
51
|
+
slots: {
|
|
52
|
+
consentWidgetAccordion: 'rounded-3xl border border-black/10',
|
|
53
|
+
consentWidgetFooter: 'border-t border-black/10 px-6',
|
|
54
|
+
toggle: 'shadow-sm',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<ConsentWidget />
|
|
60
|
+
</ConsentManagerProvider>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Widget copy should be changed through `ConsentManagerProvider.options.i18n` so the inline UI stays aligned with the rest of the consent experience.
|
|
64
|
+
|
|
65
|
+
## Advanced: Compound Components
|
|
66
|
+
|
|
67
|
+
Use compound components only when you need to rearrange the widget's existing primitives:
|
|
41
68
|
|
|
42
69
|
```tsx
|
|
43
70
|
<ConsentWidget.Root>
|
|
@@ -68,6 +95,8 @@ Build fully custom widget layouts using sub-components:
|
|
|
68
95
|
* `ConsentWidget.RejectButton` — Rejects all consent
|
|
69
96
|
* `ConsentWidget.SaveButton` — Saves custom selections
|
|
70
97
|
|
|
98
|
+
If the stock widget structure is already correct, stay with tokens and slots instead of rebuilding the layout.
|
|
99
|
+
|
|
71
100
|
## Props
|
|
72
101
|
|
|
73
102
|
### ConsentWidgetProps
|
package/docs/headless.md
CHANGED
|
@@ -4,11 +4,12 @@ description: Build fully custom consent UI using only hooks - no pre-built compo
|
|
|
4
4
|
---
|
|
5
5
|
c15t's headless mode means using the hooks (`useConsentManager`, `useTranslations`, etc.) without any pre-built UI components. This gives you complete control over the consent experience.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Before you go headless, walk the customization ladder in order:
|
|
8
8
|
|
|
9
|
-
1. **
|
|
10
|
-
2. **
|
|
11
|
-
3.
|
|
9
|
+
1. **Pre-built components** - Use provider options, component props, tokens, slots, and `theme.consentActions`
|
|
10
|
+
2. **Compound components** - Rearrange c15t primitives when the markup order must change
|
|
11
|
+
3. **`noStyle`** - Keep c15t structure but replace its styling
|
|
12
|
+
4. **Headless** - Use only hooks and build the entire UI yourself
|
|
12
13
|
|
|
13
14
|
## When to Go Headless
|
|
14
15
|
|
|
@@ -18,10 +19,15 @@ Go headless when:
|
|
|
18
19
|
* You need a consent flow that doesn't fit the banner/dialog pattern
|
|
19
20
|
* You want to embed consent choices inline rather than as overlays
|
|
20
21
|
|
|
21
|
-
Use
|
|
22
|
+
Use a lower-power tool instead when:
|
|
22
23
|
|
|
23
|
-
* The component structure works but the styling doesn't
|
|
24
|
-
* You
|
|
24
|
+
* The component structure works but the styling doesn't -> use tokens, slots, or `noStyle`
|
|
25
|
+
* You only need to rearrange existing c15t parts -> use compound components
|
|
26
|
+
* You want to change copy -> use `ConsentManagerProvider.options.i18n`
|
|
27
|
+
* You only need to restyle stock actions -> use `theme.consentActions`
|
|
28
|
+
|
|
29
|
+
> ⚠️ **Warning:**
|
|
30
|
+
> Headless mode is not the first answer for pure theming. If you are still trying to debug why a banner footer color did not change, stay in the styling system and verify the token-to-component mapping before you rebuild the UI.
|
|
25
31
|
|
|
26
32
|
> ℹ️ **Info:**
|
|
27
33
|
> Need a policy-aware implementation guide? See Building Headless Components.
|
|
@@ -130,7 +130,7 @@ Policy-driven UI hints for the consent banner surface.
|
|
|
130
130
|
|Property|Type|Description|Default|Required|
|
|
131
131
|
|:--|:--|:--|:--|:--:|
|
|
132
132
|
|allowedActions|PolicyUiAction \|undefined|Allowed actions for this surface derived from backend runtime policy.|-|Optional|
|
|
133
|
-
|
|
|
133
|
+
|primaryActions|PolicyUiAction \|undefined|Preferred primary action hints from backend runtime policy.|-|Optional|
|
|
134
134
|
|layout|PolicyUiActionGroup \|undefined|Explicit grouped action layout hint from backend runtime policy.|-|Optional|
|
|
135
135
|
|direction|PolicyUiActionDirection \|undefined|Direction hint for the grouped action layout.|-|Optional|
|
|
136
136
|
|uiProfile|PolicyUiProfile \|undefined|Presentation profile hint from backend runtime policy.|-|Optional|
|
|
@@ -143,7 +143,7 @@ Policy-driven UI hints for the consent dialog surface.
|
|
|
143
143
|
|Property|Type|Description|Default|Required|
|
|
144
144
|
|:--|:--|:--|:--|:--:|
|
|
145
145
|
|allowedActions|PolicyUiAction \|undefined|Allowed actions for this surface derived from backend runtime policy.|-|Optional|
|
|
146
|
-
|
|
|
146
|
+
|primaryActions|PolicyUiAction \|undefined|Preferred primary action hints from backend runtime policy.|-|Optional|
|
|
147
147
|
|layout|PolicyUiActionGroup \|undefined|Explicit grouped action layout hint from backend runtime policy.|-|Optional|
|
|
148
148
|
|direction|PolicyUiActionDirection \|undefined|Direction hint for the grouped action layout.|-|Optional|
|
|
149
149
|
|uiProfile|PolicyUiProfile \|undefined|Presentation profile hint from backend runtime policy.|-|Optional|
|
|
@@ -45,6 +45,7 @@ The returned `Translations` object has these sections:
|
|
|
45
45
|
|rejectAll|string \|undefined|-|-|✅ Required|
|
|
46
46
|
|customize|string \|undefined|-|-|✅ Required|
|
|
47
47
|
|save|string \|undefined|-|-|✅ Required|
|
|
48
|
+
|close|string \|undefined|-|-|✅ Required|
|
|
48
49
|
|
|
49
50
|
#### `cookieBanner` CookieBannerTranslations
|
|
50
51
|
|
|
@@ -90,6 +90,6 @@ Options: `'reject'`, `'accept'`, `'customize'` (default: `'customize'`)
|
|
|
90
90
|
|disableAnimation|boolean \|undefined|When true, disables entrance/exit animations.|false|Optional|
|
|
91
91
|
|scrollLock|boolean \|undefined|When true, locks page scroll when the banner is visible.|true|Optional|
|
|
92
92
|
|trapFocus|boolean \|undefined|When true, traps keyboard focus within the banner.|true|Optional|
|
|
93
|
-
|primaryButton|"
|
|
93
|
+
|primaryButton|"reject" \|"accept" \|"customize" \|undefined|Specifies which button should be highlighted as primary.|'customize'|Optional|
|
|
94
94
|
|models|Model \|undefined|Which consent models this banner responds to.|\['iab']|Optional|
|
|
95
95
|
|uiSource|string \|undefined|Override the UI source identifier sent with consent API calls.|'iab\_banner'|Optional|
|
package/docs/quickstart.md
CHANGED
|
@@ -33,7 +33,17 @@ availableIn:
|
|
|
33
33
|
|yarn|`yarn add @c15t/nextjs`|
|
|
34
34
|
|bun|`bun add @c15t/nextjs`|
|
|
35
35
|
|
|
36
|
-
2. **
|
|
36
|
+
2. **Import styles** Import the prebuilt component stylesheet in your root layout. This is required for styled components to render correctly.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import '@c15t/nextjs/styles.css';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> ℹ️ Info:
|
|
43
|
+
>
|
|
44
|
+
> If you are using the headless API or fully custom styling, you can skip this import.
|
|
45
|
+
|
|
46
|
+
3. **Create ConsentManager components** Create a provider component with the consent UI and a wrapper that re-exports it. This initializes the consent store and makes consent state available to all child components.
|
|
37
47
|
|
|
38
48
|
```tsx
|
|
39
49
|
'use client';
|
|
@@ -77,7 +87,7 @@ availableIn:
|
|
|
77
87
|
>
|
|
78
88
|
> Don't have a backend yet? You can use mode: 'offline' for local-only consent storage, but review the browser-only storage consequences before choosing it for production.
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
4. **Mount ConsentManager at the app root** Wrap your app tree with ConsentManager so all routes/components can access consent state.
|
|
81
91
|
|
|
82
92
|
```tsx
|
|
83
93
|
import { ConsentManager } from '@/components/consent-manager';
|
|
@@ -93,7 +103,7 @@ availableIn:
|
|
|
93
103
|
}
|
|
94
104
|
```
|
|
95
105
|
|
|
96
|
-
|
|
106
|
+
5. **Verify it works** Start your development server and confirm:
|
|
97
107
|
|
|
98
108
|
A consent banner appears at the bottom of the pageClicking "Customize" opens a dialog with toggles for each consent categoryAfter accepting or rejecting, the banner dismisses and your choice persists across page reloads
|
|
99
109
|
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
title: Class Names
|
|
3
3
|
description: Style consent components using className props and per-slot className targeting via the theme.
|
|
4
4
|
---
|
|
5
|
-
##
|
|
5
|
+
## Prefer Slots for Stock Components
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
There is no single top-level `className` contract across every pre-built consent component.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
<ConsentBanner className="my-banner" />
|
|
11
|
-
<ConsentDialog className="my-dialog" />
|
|
12
|
-
<ConsentWidget className="my-widget" />
|
|
13
|
-
```
|
|
9
|
+
For the stock `ConsentBanner`, `ConsentDialog`, and `ConsentWidget`, prefer `theme.slots` first. That keeps the markup intact and lets you target the exact part you need.
|
|
14
10
|
|
|
15
11
|
## Per-Slot className
|
|
16
12
|
|
|
@@ -59,9 +55,19 @@ const theme = {
|
|
|
59
55
|
}
|
|
60
56
|
```
|
|
61
57
|
|
|
62
|
-
##
|
|
58
|
+
## When to Use Raw className
|
|
59
|
+
|
|
60
|
+
Use raw className-level styling when:
|
|
61
|
+
|
|
62
|
+
* your styling system is already class-driven
|
|
63
|
+
* tokens are too broad for the change
|
|
64
|
+
* slots already identify the correct element
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
If the request is "make the banner footer darker", prefer `theme.colors.surfaceHover` first. If the request is "add a border and spacing only to the footer", prefer `theme.slots.consentBannerFooter`.
|
|
67
|
+
|
|
68
|
+
## Advanced: `noStyle`
|
|
69
|
+
|
|
70
|
+
Use `noStyle` only when you want to remove defaults and style from scratch while still keeping c15t's component structure:
|
|
65
71
|
|
|
66
72
|
```tsx
|
|
67
73
|
{/* Remove all styles from a specific component */}
|
|
@@ -81,4 +87,6 @@ const theme = {
|
|
|
81
87
|
} satisfies Theme;
|
|
82
88
|
```
|
|
83
89
|
|
|
90
|
+
Treat `noStyle` as an advanced escape hatch. Do not jump to it just because a token or slot needs debugging.
|
|
91
|
+
|
|
84
92
|
For full custom markup and behavior, continue to [Headless Mode](../headless).
|
package/docs/styling/overview.md
CHANGED
|
@@ -2,24 +2,34 @@
|
|
|
2
2
|
title: Styling Overview
|
|
3
3
|
description: Five approaches for theming consent components — design tokens, component slots, CSS variables, className, and noStyle mode.
|
|
4
4
|
---
|
|
5
|
-
c15t's theming system gives you multiple levels of control,
|
|
5
|
+
c15t's theming system gives you multiple levels of control, but most customization should stay inside the pre-built components.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Start with the lowest-power tool that solves the problem:
|
|
8
8
|
|
|
9
|
-
1. **
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
9
|
+
1. **Pre-built component APIs** — provider options and component props such as `layout`, `direction`, `primaryButton`, `legalLinks`, and `theme.consentActions`
|
|
10
|
+
2. **Design tokens** — global colors, typography, spacing, radius, shadows, and motion
|
|
11
|
+
3. **Slots** — targeted styling for specific parts such as the banner card, footer, or title
|
|
12
|
+
4. **CSS variables or className-level overrides** — when you need to integrate with external CSS systems
|
|
13
|
+
5. **Compound components** — when you must rearrange markup while still using c15t primitives
|
|
14
|
+
6. **`noStyle`** — when you want c15t structure but you need to own all visual styling
|
|
15
|
+
7. **Headless** — when you want fully custom markup and behavior
|
|
16
|
+
|
|
17
|
+
Keep styling and escalation as separate decisions:
|
|
18
|
+
|
|
19
|
+
* If you are still using the stock banner, dialog, or widget, stay with props, tokens, and slots.
|
|
20
|
+
* Escalate to compound components, `noStyle`, or headless only when the structure or behavior itself must change.
|
|
13
21
|
|
|
14
22
|
## Styling Approaches
|
|
15
23
|
|
|
16
24
|
|Approach|Control|Use When|
|
|
17
25
|
|--|--|--|
|
|
26
|
+
|**Component and provider APIs**|High|Reordering actions, changing button emphasis, configuring links, hiding branding, changing copy via `i18n`|
|
|
18
27
|
|**Tokens**|High|Changing global colors, typography, spacing, radius, shadows, or motion|
|
|
19
|
-
|**Slots**|Medium|Targeting specific component parts (
|
|
20
|
-
|**CSS
|
|
21
|
-
|**
|
|
22
|
-
|**noStyle**|Full|
|
|
28
|
+
|**Slots**|Medium|Targeting specific component parts (for example `consentBannerFooter` or `consentDialogCard`)|
|
|
29
|
+
|**CSS variables / className**|Medium|Integrating with an existing stylesheet or utility classes after tokens and slots|
|
|
30
|
+
|**Compound components**|Structure|Rearranging existing c15t primitives without going fully custom|
|
|
31
|
+
|**noStyle**|Full visuals|Keeping c15t structure but replacing all visual defaults|
|
|
32
|
+
|**Headless**|Full|Replacing both markup and behavior|
|
|
23
33
|
|
|
24
34
|
## Quick Start
|
|
25
35
|
|
|
@@ -58,9 +68,25 @@ export function ConsentManager({ children }) {
|
|
|
58
68
|
}
|
|
59
69
|
```
|
|
60
70
|
|
|
61
|
-
## Styling
|
|
71
|
+
## Styling Inside Pre-Built Components
|
|
72
|
+
|
|
73
|
+
Start here before you consider compound components or headless mode.
|
|
74
|
+
|
|
75
|
+
### 1. Provider and component configuration
|
|
76
|
+
|
|
77
|
+
Use the stock APIs first:
|
|
78
|
+
|
|
79
|
+
* `layout`, `direction`, and `primaryButton` for banner action arrangement
|
|
80
|
+
* `legalLinks` for link visibility
|
|
81
|
+
* `hideBranding` and `showTrigger` for dialog and widget behavior
|
|
82
|
+
* `theme.consentActions` for stock banner and dialog button treatment
|
|
83
|
+
* `i18n` on `ConsentManagerProvider` for copy changes
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<ConsentBanner layout={['customize', ['reject', 'accept']]} primaryButton="accept" />
|
|
87
|
+
```
|
|
62
88
|
|
|
63
|
-
###
|
|
89
|
+
### 2. Design tokens
|
|
64
90
|
|
|
65
91
|
Set global values for colors, typography, spacing, radius, shadows, and motion:
|
|
66
92
|
|
|
@@ -68,55 +94,140 @@ Set global values for colors, typography, spacing, radius, shadows, and motion:
|
|
|
68
94
|
options={{ theme: { colors: { primary: '#6366f1' } } }}
|
|
69
95
|
```
|
|
70
96
|
|
|
71
|
-
|
|
97
|
+
Use tokens first when the change is semantic:
|
|
98
|
+
|
|
99
|
+
* Banner card background -> `theme.colors.surface`
|
|
100
|
+
* Banner footer background -> `theme.colors.surfaceHover`
|
|
101
|
+
* Shared copy color -> `theme.colors.text` and `theme.colors.textMuted`
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
options={{
|
|
105
|
+
theme: {
|
|
106
|
+
colors: {
|
|
107
|
+
surface: '#ffffff',
|
|
108
|
+
surfaceHover: '#f6f3ee',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Component slots
|
|
72
115
|
|
|
73
116
|
Target specific component parts via the `slots` object:
|
|
74
117
|
|
|
75
118
|
```tsx
|
|
76
|
-
options={{
|
|
119
|
+
options={{
|
|
120
|
+
theme: {
|
|
121
|
+
slots: {
|
|
122
|
+
consentBannerCard: 'rounded-[28px] shadow-xl',
|
|
123
|
+
consentBannerFooter: 'border-t border-black/10',
|
|
124
|
+
consentBannerTitle: 'tracking-tight',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}}
|
|
77
128
|
```
|
|
78
129
|
|
|
79
|
-
|
|
130
|
+
Use slots when the component part is right but the local styling needs adjustment.
|
|
80
131
|
|
|
81
|
-
|
|
132
|
+
### 4. CSS variables and className-level overrides
|
|
82
133
|
|
|
83
|
-
|
|
134
|
+
Override `--c15t-*` custom properties in your stylesheet or attach classes through slots when your app styling is driven externally.
|
|
84
135
|
|
|
85
|
-
|
|
136
|
+
Reach for this after tokens and slots, not before.
|
|
86
137
|
|
|
87
138
|
```tsx
|
|
88
|
-
|
|
139
|
+
options={{
|
|
140
|
+
theme: {
|
|
141
|
+
slots: {
|
|
142
|
+
consentBannerFooter: 'bg-[var(--banner-footer)]',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}}
|
|
89
146
|
```
|
|
90
147
|
|
|
91
|
-
|
|
148
|
+
## Escalating Beyond Pre-Built Components
|
|
149
|
+
|
|
150
|
+
Only move up this ladder when the lower rung cannot satisfy the request.
|
|
92
151
|
|
|
93
|
-
|
|
152
|
+
### 5. Compound components
|
|
153
|
+
|
|
154
|
+
Use compound components when you need to rearrange existing c15t primitives:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<ConsentBanner.Root>
|
|
158
|
+
<ConsentBanner.Card>
|
|
159
|
+
<ConsentBanner.Header>
|
|
160
|
+
<ConsentBanner.Title />
|
|
161
|
+
<ConsentBanner.Description />
|
|
162
|
+
</ConsentBanner.Header>
|
|
163
|
+
<ConsentBanner.Footer>
|
|
164
|
+
<ConsentBanner.CustomizeButton />
|
|
165
|
+
<ConsentBanner.FooterSubGroup>
|
|
166
|
+
<ConsentBanner.RejectButton />
|
|
167
|
+
<ConsentBanner.AcceptButton />
|
|
168
|
+
</ConsentBanner.FooterSubGroup>
|
|
169
|
+
</ConsentBanner.Footer>
|
|
170
|
+
</ConsentBanner.Card>
|
|
171
|
+
</ConsentBanner.Root>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 6. `noStyle`
|
|
175
|
+
|
|
176
|
+
Use `noStyle` only when the c15t structure is still correct but you want to replace all visual defaults:
|
|
94
177
|
|
|
95
178
|
```tsx
|
|
96
179
|
<ConsentBanner noStyle />
|
|
97
180
|
```
|
|
98
181
|
|
|
182
|
+
### 7. Headless
|
|
183
|
+
|
|
184
|
+
Go headless only when you are replacing both markup and behavior. For that path, continue to [Headless Mode](../headless).
|
|
185
|
+
|
|
99
186
|
## Common Styling Tasks
|
|
100
187
|
|
|
101
|
-
### Change
|
|
188
|
+
### Change the banner footer background
|
|
102
189
|
|
|
103
190
|
```tsx
|
|
104
|
-
options={{
|
|
191
|
+
options={{
|
|
192
|
+
theme: {
|
|
193
|
+
colors: {
|
|
194
|
+
surfaceHover: '#f6f3ee',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
}}
|
|
105
198
|
```
|
|
106
199
|
|
|
107
|
-
|
|
200
|
+
Use `theme.colors.surfaceHover` before trying raw CSS.
|
|
201
|
+
|
|
202
|
+
### Change the banner card background
|
|
108
203
|
|
|
109
204
|
```tsx
|
|
110
|
-
options={{
|
|
205
|
+
options={{
|
|
206
|
+
theme: {
|
|
207
|
+
colors: {
|
|
208
|
+
surface: '#fffdf8',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
}}
|
|
111
212
|
```
|
|
112
213
|
|
|
113
|
-
|
|
214
|
+
Use `theme.colors.surface` before overriding banner CSS variables directly.
|
|
215
|
+
|
|
216
|
+
### Tweak the banner card, footer, or title styling without changing markup
|
|
114
217
|
|
|
115
218
|
```tsx
|
|
116
|
-
options={{
|
|
219
|
+
options={{
|
|
220
|
+
theme: {
|
|
221
|
+
slots: {
|
|
222
|
+
consentBannerCard: 'rounded-[28px] shadow-xl',
|
|
223
|
+
consentBannerFooter: 'border-t border-black/10 px-6',
|
|
224
|
+
consentBannerTitle: 'text-xl tracking-tight',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}}
|
|
117
228
|
```
|
|
118
229
|
|
|
119
|
-
### Change consent action button styles semantically
|
|
230
|
+
### Change stock consent action button styles semantically
|
|
120
231
|
|
|
121
232
|
```tsx
|
|
122
233
|
options={{
|
|
@@ -132,6 +243,29 @@ options={{
|
|
|
132
243
|
|
|
133
244
|
Use `theme.consentActions` when you want to change the stock banner/dialog button treatment without rewriting the component layout. Policy packs still control action arrangement and primary-action hints. The theme controls whether those actions render as `stroke`, `filled`, `ghost`, or `lighter`.
|
|
134
245
|
|
|
246
|
+
### Change banner copy without replacing the component
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
options={{
|
|
250
|
+
i18n: {
|
|
251
|
+
locale: 'en',
|
|
252
|
+
messages: {
|
|
253
|
+
en: {
|
|
254
|
+
cookieBanner: {
|
|
255
|
+
title: 'We value your privacy',
|
|
256
|
+
description: 'We use cookies to improve the site and measure performance.',
|
|
257
|
+
},
|
|
258
|
+
common: {
|
|
259
|
+
acceptAll: 'Accept all',
|
|
260
|
+
rejectAll: 'Reject all',
|
|
261
|
+
customize: 'Manage preferences',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
}}
|
|
267
|
+
```
|
|
268
|
+
|
|
135
269
|
### Enable dark mode safely
|
|
136
270
|
|
|
137
271
|
```tsx
|
|
@@ -144,8 +278,11 @@ options={{
|
|
|
144
278
|
}}
|
|
145
279
|
```
|
|
146
280
|
|
|
281
|
+
> ℹ️ **Info:**
|
|
282
|
+
> If a token change does not show up where you expect, check how that component maps tokens to CSS variables before escalating. For example, the stock banner footer background comes from colors.surfaceHover, not a separate footer token.
|
|
283
|
+
>
|
|
147
284
|
> ⚠️ **Warning:**
|
|
148
|
-
> noStyle: true removes layout and visual defaults.
|
|
285
|
+
> Do not jump to CSS overrides or !important because a token did not appear to work at first glance.noStyle: true removes layout and visual defaults. Treat it as an advanced opt-out, not a normal theming step.Headless mode is for replacing markup and behavior, not for styling-only requests.Use either tokens/slots or raw CSS variable overrides intentionally to avoid conflicting style sources.For dark mode, c15t supports .dark and .c15t-dark.
|
|
149
286
|
|
|
150
287
|
## API Reference
|
|
151
288
|
|
package/docs/styling/slots.md
CHANGED
|
@@ -4,7 +4,19 @@ description: Target individual component parts with styles using the slot system
|
|
|
4
4
|
---
|
|
5
5
|
## What are Slots?
|
|
6
6
|
|
|
7
|
-
Slots let you target specific parts of consent components with styles. Each component is built from named
|
|
7
|
+
Slots let you target specific parts of consent components with styles. Each component is built from named slots such as `consentBannerTitle` and `consentDialogFooter`.
|
|
8
|
+
|
|
9
|
+
Use slots after the stock component APIs and design tokens:
|
|
10
|
+
|
|
11
|
+
* If the change is semantic, prefer tokens first. For example, the stock banner footer background comes from `theme.colors.surfaceHover`.
|
|
12
|
+
* If the component part is correct but you need a local tweak, use a slot.
|
|
13
|
+
|
|
14
|
+
Common banner slot choices:
|
|
15
|
+
|
|
16
|
+
* `consentBannerCard` for card radius, shadow, width, and local background treatment
|
|
17
|
+
* `consentBannerFooter` for spacing, borders, and local footer styling
|
|
18
|
+
* `consentBannerTitle` for title typography
|
|
19
|
+
* `buttonPrimary` and `buttonSecondary` for shared button classes
|
|
8
20
|
|
|
9
21
|
## Using Slots
|
|
10
22
|
|
|
@@ -13,8 +25,8 @@ Pass slot styles in the theme's `slots` object:
|
|
|
13
25
|
```tsx
|
|
14
26
|
const theme = {
|
|
15
27
|
slots: {
|
|
16
|
-
|
|
17
|
-
consentBannerTitle: 'text-xl font-bold
|
|
28
|
+
consentBannerFooter: 'border-t border-black/10 px-6',
|
|
29
|
+
consentBannerTitle: 'text-xl font-bold tracking-tight',
|
|
18
30
|
|
|
19
31
|
// Object value = className + inline styles
|
|
20
32
|
consentBannerCard: {
|
|
@@ -34,13 +46,31 @@ Each slot accepts either a `string` (treated as className) or a `SlotStyle` obje
|
|
|
34
46
|
consentBannerTitle: 'my-custom-class'
|
|
35
47
|
|
|
36
48
|
// Object: className + style + noStyle
|
|
37
|
-
|
|
38
|
-
className: '
|
|
39
|
-
style: {
|
|
40
|
-
noStyle: false, // Set true
|
|
49
|
+
consentBannerFooter: {
|
|
50
|
+
className: 'border-t border-black/10',
|
|
51
|
+
style: { paddingBlock: '1rem' },
|
|
52
|
+
noStyle: false, // Set true only when you want to remove this slot's default styling
|
|
41
53
|
}
|
|
42
54
|
```
|
|
43
55
|
|
|
56
|
+
`noStyle` on a slot is an advanced escape hatch. Start with className and style overrides first.
|
|
57
|
+
|
|
58
|
+
## Example: Style the stock banner without changing markup
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const theme = {
|
|
62
|
+
colors: {
|
|
63
|
+
surface: '#fffdf8',
|
|
64
|
+
surfaceHover: '#f6f3ee',
|
|
65
|
+
},
|
|
66
|
+
slots: {
|
|
67
|
+
consentBannerCard: 'rounded-[28px] shadow-xl',
|
|
68
|
+
consentBannerFooter: 'border-t border-black/10 px-6',
|
|
69
|
+
consentBannerTitle: 'tracking-tight',
|
|
70
|
+
},
|
|
71
|
+
} satisfies Theme;
|
|
72
|
+
```
|
|
73
|
+
|
|
44
74
|
## Available Slots
|
|
45
75
|
|
|
46
76
|
Use the typed API reference below for the full slot list and descriptions. It stays in sync with the actual component slot surface.
|
package/docs/styling/tailwind.md
CHANGED
|
@@ -6,21 +6,42 @@ c15t works with Tailwind CSS out of the box. Use the `slots` theme option to app
|
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
|
|
9
|
+
Import the standard c15t stylesheet once at the root of your app:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// React
|
|
13
|
+
import '@c15t/react/styles.css';
|
|
14
|
+
|
|
15
|
+
// Next.js
|
|
16
|
+
import '@c15t/nextjs/styles.css';
|
|
17
|
+
```
|
|
18
|
+
|
|
9
19
|
### Tailwind v4
|
|
10
20
|
|
|
11
|
-
|
|
21
|
+
Tailwind v4 automatically scans your source files. Import Tailwind normally. c15t component styles join Tailwind's `components` layer automatically, so no extra c15t-specific layer declaration is needed:
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
@import "tailwindcss";
|
|
25
|
+
```
|
|
12
26
|
|
|
13
27
|
### Tailwind v3
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
Import the Tailwind 3-compatible c15t stylesheet before your app Tailwind globals, then keep your standard Tailwind directives in the app stylesheet:
|
|
30
|
+
|
|
31
|
+
```tsx title="app/layout.tsx"
|
|
32
|
+
import '@c15t/react/styles.tw3.css';
|
|
33
|
+
import './globals.css';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```tsx title="app/layout.tsx"
|
|
37
|
+
import '@c15t/nextjs/styles.tw3.css';
|
|
38
|
+
import './globals.css';
|
|
39
|
+
```
|
|
16
40
|
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'./node_modules/@c15t/react/**/*.{js,mjs}',
|
|
22
|
-
],
|
|
23
|
-
};
|
|
41
|
+
```css title="app/globals.css"
|
|
42
|
+
@tailwind base;
|
|
43
|
+
@tailwind components;
|
|
44
|
+
@tailwind utilities;
|
|
24
45
|
```
|
|
25
46
|
|
|
26
47
|
## Using Tailwind with Slots
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/nextjs",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.7",
|
|
4
4
|
"description": "Developer-first CMP for Next.js: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -27,9 +27,15 @@
|
|
|
27
27
|
"directory": "packages/nextjs"
|
|
28
28
|
},
|
|
29
29
|
"license": "GPL-3.0-only",
|
|
30
|
-
"sideEffects":
|
|
30
|
+
"sideEffects": [
|
|
31
|
+
"**/*.css"
|
|
32
|
+
],
|
|
31
33
|
"type": "module",
|
|
32
34
|
"exports": {
|
|
35
|
+
"./styles.css": "./dist/styles.css",
|
|
36
|
+
"./styles.tw3.css": "./src/styles.tw3.css",
|
|
37
|
+
"./iab/styles.css": "./dist/iab/styles.css",
|
|
38
|
+
"./iab/styles.tw3.css": "./src/iab/styles.tw3.css",
|
|
33
39
|
"./headless": {
|
|
34
40
|
"types": "./dist-types/headless.d.ts",
|
|
35
41
|
"import": "./dist/headless.js",
|
|
@@ -47,13 +53,17 @@
|
|
|
47
53
|
}
|
|
48
54
|
},
|
|
49
55
|
"main": "./dist/index.js",
|
|
50
|
-
"module": "./dist/index.
|
|
56
|
+
"module": "./dist/index.js",
|
|
51
57
|
"types": "./dist-types/index.d.ts",
|
|
52
58
|
"files": [
|
|
53
59
|
"dist",
|
|
54
60
|
"docs",
|
|
55
61
|
"dist-types",
|
|
56
|
-
"client"
|
|
62
|
+
"client",
|
|
63
|
+
"src/styles.css",
|
|
64
|
+
"src/styles.tw3.css",
|
|
65
|
+
"src/iab/styles.css",
|
|
66
|
+
"src/iab/styles.tw3.css"
|
|
57
67
|
],
|
|
58
68
|
"scripts": {
|
|
59
69
|
"prebuild": "genversion --esm --semi src/version.ts",
|
|
@@ -68,16 +78,15 @@
|
|
|
68
78
|
"test:watch": "bun prebuild && vitest --passWithNoTests"
|
|
69
79
|
},
|
|
70
80
|
"dependencies": {
|
|
71
|
-
"@c15t/react": "2.0.0-rc.
|
|
81
|
+
"@c15t/react": "2.0.0-rc.7",
|
|
72
82
|
"@c15t/translations": "2.0.0-rc.5",
|
|
73
|
-
"c15t": "2.0.0-rc.
|
|
83
|
+
"c15t": "2.0.0-rc.6"
|
|
74
84
|
},
|
|
75
85
|
"devDependencies": {
|
|
76
|
-
"@c15t/backend": "2.0.0-rc.5",
|
|
77
86
|
"@c15t/typescript-config": "0.0.1-beta.1",
|
|
78
87
|
"@c15t/vitest-config": "1.0.0",
|
|
79
88
|
"genversion": "3.2.0",
|
|
80
|
-
"typescript": "
|
|
89
|
+
"typescript": "6.0.2"
|
|
81
90
|
},
|
|
82
91
|
"peerDependencies": {
|
|
83
92
|
"next": "^16.0.0 || ^15.0.0 || ^14.0.0 || ^13.0.0",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @c15t/nextjs — IAB TCF component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import this stylesheet when using IAB consent components in Next.js.
|
|
5
|
+
* Self-contained — includes shared primitives, no need for base styles.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import '@c15t/nextjs/iab/styles.css';
|
|
9
|
+
*/
|
|
10
|
+
@import "@c15t/react/iab/styles.css";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @c15t/nextjs/iab — Tailwind 3-compatible IAB component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import this stylesheet before your app Tailwind globals so utility classes
|
|
5
|
+
* can come after the c15t base rules.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import '@c15t/nextjs/iab/styles.tw3.css';
|
|
9
|
+
*/
|
|
10
|
+
@import "@c15t/react/iab/styles.tw3.css";
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @c15t/nextjs — Non-IAB prebuilt component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import this stylesheet once in your root layout when using
|
|
5
|
+
* prebuilt (styled) consent components.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import '@c15t/nextjs/styles.css';
|
|
9
|
+
*/
|
|
10
|
+
@import "@c15t/react/styles.css";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @c15t/nextjs — Tailwind 3-compatible prebuilt component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import this stylesheet before your app Tailwind globals so utility classes
|
|
5
|
+
* can come after the c15t base rules.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import '@c15t/nextjs/styles.tw3.css';
|
|
9
|
+
*/
|
|
10
|
+
@import "@c15t/react/styles.tw3.css";
|