@c15t/nextjs 2.0.0-rc.1 → 2.0.0-rc.10

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 (98) hide show
  1. package/README.md +10 -3
  2. package/client/components/consent-dialog-link.js +3 -0
  3. package/dist/headless.cjs +1 -1
  4. package/dist/iab/styles.css +12 -0
  5. package/dist/iab/styles.tw3.css +14 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/libs/browser-initial-data.cjs +1 -0
  9. package/dist/libs/browser-initial-data.js +1 -0
  10. package/dist/libs/initial-data.cjs +1 -1
  11. package/dist/libs/initial-data.js +1 -1
  12. package/dist/styles.css +10 -0
  13. package/dist/styles.tw3.css +13 -0
  14. package/dist/types.cjs +1 -1
  15. package/dist/version.cjs +1 -1
  16. package/dist/version.js +1 -1
  17. package/{dist → dist-types}/headless.d.ts +0 -1
  18. package/{dist → dist-types}/index.d.ts +3 -2
  19. package/dist-types/libs/browser-initial-data.d.ts +9 -0
  20. package/{dist → dist-types}/libs/initial-data.d.ts +7 -2
  21. package/dist-types/types.d.ts +38 -0
  22. package/dist-types/version.d.ts +1 -0
  23. package/docs/README.md +73 -0
  24. package/docs/building-headless-components.md +377 -0
  25. package/docs/callbacks.md +184 -0
  26. package/docs/components/consent-banner.md +269 -0
  27. package/docs/components/consent-dialog-link.md +59 -0
  28. package/docs/components/consent-dialog-trigger.md +103 -0
  29. package/docs/components/consent-dialog.md +177 -0
  30. package/docs/components/consent-manager-provider.md +425 -0
  31. package/docs/components/consent-widget.md +133 -0
  32. package/docs/components/dev-tools.md +63 -0
  33. package/docs/components/frame.md +73 -0
  34. package/docs/concepts/client-modes.md +175 -0
  35. package/docs/concepts/consent-categories.md +97 -0
  36. package/docs/concepts/consent-models.md +116 -0
  37. package/docs/concepts/cookie-management.md +122 -0
  38. package/docs/concepts/glossary.md +23 -0
  39. package/docs/concepts/initialization-flow.md +148 -0
  40. package/docs/concepts/policy-packs.md +229 -0
  41. package/docs/headless.md +190 -0
  42. package/docs/hooks/use-color-scheme.md +40 -0
  43. package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
  44. package/docs/hooks/use-consent-manager/location-info.md +95 -0
  45. package/docs/hooks/use-consent-manager/overview.md +420 -0
  46. package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
  47. package/docs/hooks/use-draggable.md +57 -0
  48. package/docs/hooks/use-focus-trap.md +41 -0
  49. package/docs/hooks/use-reduced-motion.md +35 -0
  50. package/docs/hooks/use-ssr-status.md +31 -0
  51. package/docs/hooks/use-text-direction.md +49 -0
  52. package/docs/hooks/use-translations.md +118 -0
  53. package/docs/iab/consent-banner.md +94 -0
  54. package/docs/iab/consent-dialog.md +134 -0
  55. package/docs/iab/overview.md +126 -0
  56. package/docs/iab/use-gvl-data.md +20 -0
  57. package/docs/iframe-blocking.md +107 -0
  58. package/docs/integrations/building-integrations.md +405 -0
  59. package/docs/integrations/databuddy.md +203 -0
  60. package/docs/integrations/google-tag-manager.md +153 -0
  61. package/docs/integrations/google-tag.md +122 -0
  62. package/docs/integrations/linkedin-insights.md +109 -0
  63. package/docs/integrations/meta-pixel.md +342 -0
  64. package/docs/integrations/microsoft-uet.md +112 -0
  65. package/docs/integrations/overview.md +105 -0
  66. package/docs/integrations/posthog.md +199 -0
  67. package/docs/integrations/tiktok-pixel.md +113 -0
  68. package/docs/integrations/x-pixel.md +143 -0
  69. package/docs/internationalization.md +197 -0
  70. package/docs/network-blocker.md +178 -0
  71. package/docs/optimization.md +234 -0
  72. package/docs/policy-packs.md +246 -0
  73. package/docs/quickstart.md +161 -0
  74. package/docs/script-loader.md +321 -0
  75. package/docs/server-side.md +176 -0
  76. package/docs/styling/classnames.md +92 -0
  77. package/docs/styling/color-scheme.md +82 -0
  78. package/docs/styling/css-variables.md +92 -0
  79. package/docs/styling/overview.md +456 -0
  80. package/docs/styling/slots.md +127 -0
  81. package/docs/styling/tailwind.md +113 -0
  82. package/docs/styling/tokens.md +216 -0
  83. package/docs/troubleshooting.md +146 -0
  84. package/iab/styles.css +1 -0
  85. package/package.json +36 -15
  86. package/readme.json +4 -0
  87. package/src/iab/styles.css +12 -0
  88. package/src/iab/styles.tw3.css +14 -0
  89. package/src/styles.css +10 -0
  90. package/src/styles.tw3.css +13 -0
  91. package/styles.css +1 -0
  92. package/dist/headless.d.ts.map +0 -1
  93. package/dist/index.d.ts.map +0 -1
  94. package/dist/libs/initial-data.d.ts.map +0 -1
  95. package/dist/types.d.ts +0 -16
  96. package/dist/types.d.ts.map +0 -1
  97. package/dist/version.d.ts +0 -2
  98. package/dist/version.d.ts.map +0 -1
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: Setting Consent
3
+ description: Save, update, and reset consent preferences with setConsent(), setSelectedConsent(), and saveConsents().
4
+ ---
5
+ ## saveConsents(type)
6
+
7
+ The primary way to persist consent. Accepts one of three strategies:
8
+
9
+ ```tsx
10
+ const { saveConsents } = useConsentManager();
11
+
12
+ // Accept all - sets every active category to true
13
+ await saveConsents('all');
14
+
15
+ // Reject all - only necessary stays true, everything else false
16
+ await saveConsents('necessary');
17
+
18
+ // Save current selections - persists whatever the user toggled
19
+ await saveConsents('custom');
20
+ ```
21
+
22
+ **What happens when you call saveConsents:**
23
+
24
+ 1. Consent state is updated in the store
25
+ 2. UI closes (activeUI → 'none')
26
+ 3. Consent is saved to localStorage and cookie
27
+ 4. If consent was revoked and `reloadOnConsentRevoked` is true, the page reloads
28
+ 5. Otherwise, scripts/iframes/network blocker are updated
29
+ 6. Consent is synced to the backend API
30
+
31
+ ## setConsent(name, value)
32
+
33
+ Updates a single consent category AND automatically saves it. Use this for simple one-off consent changes:
34
+
35
+ ```tsx
36
+ const { setConsent } = useConsentManager();
37
+
38
+ // Grant measurement consent immediately
39
+ setConsent('measurement', true);
40
+
41
+ // Revoke marketing consent immediately
42
+ setConsent('marketing', false);
43
+ ```
44
+
45
+ ## setSelectedConsent(name, value)
46
+
47
+ Updates the selection state without saving. This is what dialog toggles use - the user can flip toggles without committing until they click "Save":
48
+
49
+ ```tsx
50
+ const { setSelectedConsent, saveConsents } = useConsentManager();
51
+
52
+ // User toggles measurement on
53
+ setSelectedConsent('measurement', true);
54
+
55
+ // User toggles marketing off
56
+ setSelectedConsent('marketing', false);
57
+
58
+ // User clicks "Save" - now it persists
59
+ await saveConsents('custom');
60
+ ```
61
+
62
+ ## resetConsents()
63
+
64
+ Resets all consent preferences to their default values and clears stored consent:
65
+
66
+ ```tsx
67
+ const { resetConsents } = useConsentManager();
68
+
69
+ resetConsents();
70
+ // All consents back to defaults, consent info cleared
71
+ ```
72
+
73
+ ## Accept All / Reject All Patterns
74
+
75
+ Common patterns for banner buttons:
76
+
77
+ ```tsx
78
+ function ConsentActions() {
79
+ const { saveConsents } = useConsentManager();
80
+
81
+ return (
82
+ <div>
83
+ <button onClick={() => saveConsents('necessary')}>
84
+ Reject All
85
+ </button>
86
+ <button onClick={() => saveConsents('all')}>
87
+ Accept All
88
+ </button>
89
+ </div>
90
+ );
91
+ }
92
+ ```
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: useDraggable
3
+ description: Make an element draggable between viewport corners with snapping, persistence, and animation support.
4
+ ---
5
+ `useDraggable()` provides drag-to-corner functionality. Used internally by `ConsentDialogTrigger`, this hook lets you build custom draggable elements that snap to viewport corners.
6
+
7
+ ```tsx
8
+ import { useDraggable } from '@c15t/nextjs';
9
+
10
+ function DraggableButton() {
11
+ const { corner, isDragging, handlers, dragStyle } = useDraggable({
12
+ defaultPosition: 'bottom-right',
13
+ persistPosition: true,
14
+ });
15
+
16
+ return (
17
+ <button
18
+ {...handlers}
19
+ style={{
20
+ ...dragStyle,
21
+ position: 'fixed',
22
+ // Position based on corner
23
+ ...(corner.includes('bottom') ? { bottom: 16 } : { top: 16 }),
24
+ ...(corner.includes('right') ? { right: 16 } : { left: 16 }),
25
+ }}
26
+ >
27
+ {isDragging ? 'Dragging...' : 'Drag me'}
28
+ </button>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ## Options
34
+
35
+ |Option|Type|Default|Description|
36
+ |--|--|--|--|
37
+ |`defaultPosition`|`CornerPosition`|`'bottom-right'`|Initial corner position|
38
+ |`persistPosition`|`boolean`|`true`|Save position to localStorage|
39
+ |`onPositionChange`|`(position: CornerPosition) => void`|-|Callback on position change|
40
+
41
+ ## Return Value
42
+
43
+ |Property|Type|Description||||
44
+ |--|--|--|--|--|--|
45
+ |`corner`|`CornerPosition`|Current corner: `'top-left'`|`'top-right'`|`'bottom-left'`|`'bottom-right'`|
46
+ |`isDragging`|`boolean`|Whether the element is being dragged||||
47
+ |`isSnapping`|`boolean`|Whether the element is animating to a new corner||||
48
+ |`wasDragged`|`() => boolean`|Whether the last interaction was a drag (vs click)||||
49
+ |`handlers`|`object`|Pointer event handlers to spread onto the element||||
50
+ |`dragStyle`|`CSSProperties`|Transform style for drag offset||||
51
+
52
+ ## Behavior
53
+
54
+ * Drag starts on pointer down (left click / single touch)
55
+ * Movement threshold of 5px distinguishes drag from click
56
+ * On pointer up, element snaps to the nearest corner based on drag direction and velocity
57
+ * Position persists to localStorage by default
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: useFocusTrap
3
+ description: Trap keyboard focus within a container for accessible modal dialogs.
4
+ ---
5
+ `useFocusTrap()` keeps keyboard focus within a container element while active. This is essential for accessibility - when a modal dialog is open, Tab and Shift+Tab should cycle through focusable elements inside the dialog, not escape to the page behind it.
6
+
7
+ ```tsx
8
+ import { useFocusTrap } from '@c15t/nextjs';
9
+ import { useRef } from 'react';
10
+
11
+ function AccessibleModal({ isOpen }: { isOpen: boolean }) {
12
+ const containerRef = useRef<HTMLDivElement>(null);
13
+ useFocusTrap(isOpen, containerRef);
14
+
15
+ if (!isOpen) return null;
16
+
17
+ return (
18
+ <div ref={containerRef} role="dialog" aria-modal="true">
19
+ <h2>Modal Title</h2>
20
+ <button>Action</button>
21
+ <button>Close</button>
22
+ </div>
23
+ );
24
+ }
25
+ ```
26
+
27
+ ## Parameters
28
+
29
+ |Parameter|Type|Description|
30
+ |--|--|--|
31
+ |`shouldTrap`|`boolean`|Whether focus should be trapped|
32
+ |`containerRef`|`RefObject<HTMLElement \|null> \|null`|Ref to the container element|
33
+
34
+ ## Behavior
35
+
36
+ * **Tab**: Moves focus to the next focusable element. Wraps to the first element when reaching the end.
37
+ * **Shift+Tab**: Moves focus to the previous focusable element. Wraps to the last element when reaching the start.
38
+ * Focus is restored to the previously focused element when the trap is deactivated.
39
+
40
+ > ℹ️ **Info:**
41
+ > ConsentBanner and ConsentDialog use useFocusTrap internally when trapFocus is enabled (default: true). You only need this hook when building custom consent UI.
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: useReducedMotion
3
+ description: Detect the user's prefers-reduced-motion OS setting and reactively disable animations.
4
+ ---
5
+ `useReducedMotion()` reads the `prefers-reduced-motion: reduce` media query and reactively updates when the user's preference changes. Use it to conditionally skip animations for users who have enabled reduced motion in their OS accessibility settings.
6
+
7
+ The hook returns `false` during SSR to avoid hydration mismatches, then updates to the actual preference on the client.
8
+
9
+ ```tsx
10
+ import { useReducedMotion } from '@c15t/react/hooks';
11
+
12
+ function AnimatedBanner() {
13
+ const prefersReducedMotion = useReducedMotion();
14
+
15
+ return (
16
+ <div
17
+ style={{
18
+ transition: prefersReducedMotion ? 'none' : 'opacity 300ms ease',
19
+ opacity: 1,
20
+ }}
21
+ >
22
+ Consent banner content
23
+ </div>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ## Return Value
29
+
30
+ |Type|Description|
31
+ |--|--|
32
+ |`boolean`|`true` if the user prefers reduced motion, `false` otherwise|
33
+
34
+ > ℹ️ **Info:**
35
+ > c15t's built-in components already respect prefers-reduced-motion internally. This hook is primarily useful when building custom UI with the headless approach.
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: useSSRStatus
3
+ description: Check whether server-side rendered consent data was used during initialization.
4
+ ---
5
+ `useSSRStatus()` returns information about whether SSR data was used for consent manager initialization. This is primarily useful for debugging SSR data flow in Next.js or other server-rendering frameworks.
6
+
7
+ ```tsx
8
+ import { useSSRStatus } from '@c15t/nextjs';
9
+
10
+ function DebugSSR() {
11
+ const { ssrDataUsed, ssrSkippedReason } = useSSRStatus();
12
+
13
+ if (ssrDataUsed) {
14
+ return <span>Consent initialized from SSR data</span>;
15
+ }
16
+
17
+ return <span>SSR skipped: {ssrSkippedReason ?? 'unknown'}</span>;
18
+ }
19
+ ```
20
+
21
+ ## Return Value
22
+
23
+ ### SSRStatus
24
+
25
+ |Property|Type|Description|Default|Required|
26
+ |:--|:--|:--|:--|:--:|
27
+ |ssrDataUsed|boolean|Whether SSR data was used for initialization. \`true\` if SSR data was provided and successfully consumed, \`false\` otherwise.|-|✅ Required|
28
+ |ssrSkippedReason|"no\_data" \|"fetch\_failed" \|"context\_mismatch" \|null|Reason SSR data was skipped, or \`null\` if used successfully.|-|✅ Required|
29
+
30
+ > ℹ️ **Info:**
31
+ > Must be used within a ConsentManagerProvider. Throws if used outside the provider context.
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: useTextDirection
3
+ description: Manage RTL/LTR text direction based on the active language for consent UI.
4
+ ---
5
+ `useTextDirection()` determines the correct text direction (`'ltr'` or `'rtl'`) based on the provided language and sets it on the document. It wraps the `@c15t/ui` text direction utilities as a React hook.
6
+
7
+ This is used internally by IAB components but is available for custom UI that needs to handle bidirectional text.
8
+
9
+ ```tsx
10
+ import { useTextDirection } from '@c15t/react/hooks';
11
+
12
+ function CustomConsentUI({ language }: { language: string }) {
13
+ const direction = useTextDirection(language);
14
+
15
+ return (
16
+ <div dir={direction}>
17
+ {/* Content renders correctly for RTL languages like Arabic and Hebrew */}
18
+ </div>
19
+ );
20
+ }
21
+ ```
22
+
23
+ ## Parameters
24
+
25
+ |Parameter|Type|Default|Description|
26
+ |--|--|--|--|
27
+ |`language`|`string \|undefined`|—|BCP 47 language tag (e.g. `'en'`, `'ar'`, `'he'`)|
28
+
29
+ ## Return Value
30
+
31
+ |Type|Description|
32
+ |--|--|
33
+ |`'ltr' \|'rtl'`|The text direction for the given language|
34
+
35
+ ## RTL Languages
36
+
37
+ The hook recognizes standard RTL languages including Arabic (`ar`), Hebrew (`he`), Persian (`fa`), and Urdu (`ur`), among others.
38
+
39
+ ```tsx
40
+ import { useConsentManager } from '@c15t/nextjs';
41
+ import { useTextDirection } from '@c15t/react/hooks';
42
+
43
+ function BiDirectionalBanner() {
44
+ const { translationConfig } = useConsentManager();
45
+ const dir = useTextDirection(translationConfig.defaultLanguage);
46
+
47
+ return <div dir={dir}>...</div>;
48
+ }
49
+ ```
@@ -0,0 +1,118 @@
1
+ ---
2
+ title: useTranslations
3
+ description: Access the current language's translations for building custom consent UI.
4
+ ---
5
+ `useTranslations()` returns the `Translations` object for the currently active language. Use it when building custom consent UI that needs translated text.
6
+
7
+ ```tsx
8
+ import { useTranslations } from '@c15t/nextjs';
9
+
10
+ function CustomBanner() {
11
+ const translations = useTranslations();
12
+
13
+ return (
14
+ <div>
15
+ <h2>{translations.cookieBanner.title}</h2>
16
+ <p>{translations.cookieBanner.description}</p>
17
+ <button>{translations.common.acceptAll}</button>
18
+ <button>{translations.common.rejectAll}</button>
19
+ </div>
20
+ );
21
+ }
22
+ ```
23
+
24
+ ## Translation Sections
25
+
26
+ The returned `Translations` object has these sections:
27
+
28
+ ### Translations
29
+
30
+ |Property|Type|Description|Default|Required|
31
+ |:--|:--|:--|:--|:--:|
32
+ |common|CommonTranslations|-|-|✅ Required|
33
+ |cookieBanner|CookieBannerTranslations|-|-|✅ Required|
34
+ |consentManagerDialog|ConsentManagerDialogTranslations|-|-|✅ Required|
35
+ |consentTypes|Object \|undefined|-|-|✅ Required|
36
+ |frame|FrameTranslations \|undefined|-|-|Optional|
37
+ |legalLinks|LegalLinksTranslations \|undefined|-|-|Optional|
38
+ |iab|Object \|undefined|-|-|Optional|
39
+
40
+ #### `common` CommonTranslations
41
+
42
+ |Property|Type|Description|Default|Required|
43
+ |:--|:--|:--|:--|:--:|
44
+ |acceptAll|string \|undefined|-|-|✅ Required|
45
+ |rejectAll|string \|undefined|-|-|✅ Required|
46
+ |customize|string \|undefined|-|-|✅ Required|
47
+ |save|string \|undefined|-|-|✅ Required|
48
+ |close|string \|undefined|-|-|✅ Required|
49
+ |securedBy|string \|undefined|-|-|✅ Required|
50
+
51
+ #### `cookieBanner` CookieBannerTranslations
52
+
53
+ |Property|Type|Description|Default|Required|
54
+ |:--|:--|:--|:--|:--:|
55
+ |title|string \|undefined|-|-|✅ Required|
56
+ |description|string \|undefined|-|-|✅ Required|
57
+
58
+ #### `consentManagerDialog` ConsentManagerDialogTranslations
59
+
60
+ |Property|Type|Description|Default|Required|
61
+ |:--|:--|:--|:--|:--:|
62
+ |title|string \|undefined|-|-|✅ Required|
63
+ |description|string \|undefined|-|-|✅ Required|
64
+
65
+ #### `consentTypes`
66
+
67
+ |Property|Type|Description|Default|Required|
68
+ |:--|:--|:--|:--|:--:|
69
+ |experience|ConsentTypeTranslations \|undefined|-|-|✅ Required|
70
+ |functionality|ConsentTypeTranslations \|undefined|-|-|✅ Required|
71
+ |marketing|ConsentTypeTranslations \|undefined|-|-|✅ Required|
72
+ |measurement|ConsentTypeTranslations \|undefined|-|-|✅ Required|
73
+ |necessary|ConsentTypeTranslations \|undefined|-|-|✅ Required|
74
+
75
+ #### `frame` FrameTranslations
76
+
77
+ |Property|Type|Description|Default|Required|
78
+ |:--|:--|:--|:--|:--:|
79
+ |title|string \|undefined|You can use the \{category} placeholder to dynamically insert the consent category name.|-|✅ Required|
80
+ |actionButton|string \|undefined|You can use the \{category} placeholder to dynamically insert the consent category name.|-|✅ Required|
81
+ |policyBlocked|string \|undefined|Message shown when the frame category is blocked by active policy scope.|-|✅ Required|
82
+
83
+ #### `legalLinks` LegalLinksTranslations
84
+
85
+ |Property|Type|Description|Default|Required|
86
+ |:--|:--|:--|:--|:--:|
87
+ |privacyPolicy|string \|undefined|-|-|✅ Required|
88
+ |cookiePolicy|string \|undefined|-|-|✅ Required|
89
+ |termsOfService|string \|undefined|-|-|✅ Required|
90
+
91
+ #### `iab`
92
+
93
+ |Property|Type|Description|Default|Required|
94
+ |:--|:--|:--|:--|:--:|
95
+ |banner|DeepPartial\<IABBannerTranslations> \|undefined|-|-|✅ Required|
96
+ |preferenceCenter|DeepPartial\<IABPreferenceCenterTranslations> \|undefined|-|-|✅ Required|
97
+ |common|DeepPartial\<IABCommonTranslations> \|undefined|-|-|✅ Required|
98
+
99
+ ## With setLanguage
100
+
101
+ Translations update automatically when the language changes:
102
+
103
+ ```tsx
104
+ import { useConsentManager, useTranslations } from '@c15t/nextjs';
105
+
106
+ function LocalizedConsent() {
107
+ const { setLanguage } = useConsentManager();
108
+ const translations = useTranslations();
109
+
110
+ return (
111
+ <div>
112
+ <p>{translations.cookieBanner.description}</p>
113
+ <button onClick={() => setLanguage('de')}>Deutsch</button>
114
+ <button onClick={() => setLanguage('fr')}>Français</button>
115
+ </div>
116
+ );
117
+ }
118
+ ```
@@ -0,0 +1,94 @@
1
+ ---
2
+ title: IABConsentBanner
3
+ description: An IAB TCF 2.3 compliant consent banner that displays partner count, purpose summaries, and legitimate interest notices.
4
+ ---
5
+ > ❌ **Error:**
6
+ > c15t is not yet IAB certified. The IAB TCF components are under active development and should not be used in production. APIs and behavior may change before certification is achieved.
7
+
8
+ `IABConsentBanner` is a pre-built consent banner that follows the [IAB Transparency & Consent Framework (TCF) 2.3](https://iabeurope.eu/tcf-2-0/) specification. It renders when the consent model is set to `'iab'` and includes required disclosures like partner count, purpose summaries, and legitimate interest notices.
9
+
10
+ Use this component instead of `ConsentBanner` when you need IAB TCF compliance for programmatic advertising in EU jurisdictions.
11
+
12
+ ## When to Use
13
+
14
+ * Your site participates in the IAB TCF ecosystem (ad exchanges, SSPs, DSPs)
15
+ * You need to disclose vendor partnerships and data processing purposes per IAB requirements
16
+ * The detected jurisdiction requires IAB TCF compliance (typically EU/EEA)
17
+
18
+ ## Basic Usage
19
+
20
+ ```tsx
21
+ import { type ReactNode } from 'react';
22
+ import { iab } from '@c15t/iab';
23
+ import { ConsentManagerProvider } from '@c15t/nextjs';
24
+ import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
25
+
26
+ export default function ConsentManager({ children }: { children: ReactNode }) {
27
+ return (
28
+ <ConsentManagerProvider
29
+ options={{
30
+ mode: 'hosted',
31
+ backendURL: '/api/c15t',
32
+ iab: iab({
33
+ vendors: [1, 2, 10, 25],
34
+ // cmpId is automatically provided by the backend when using consent.io.
35
+ // Only set this if you have your own CMP registration.
36
+ // cmpId: 123,
37
+ }),
38
+ }}
39
+ >
40
+ <IABConsentBanner />
41
+ <IABConsentDialog />
42
+ {children}
43
+ </ConsentManagerProvider>
44
+ );
45
+ }
46
+ ```
47
+
48
+ > ℹ️ **Info:**
49
+ > The banner only renders when IAB mode is enabled and the GVL (Global Vendor List) has been loaded. If iab.enabled is false or the server does not return GVL data, nothing is rendered.
50
+
51
+ ## Banner Content
52
+
53
+ The IAB banner automatically displays:
54
+
55
+ * **Title** — Heading text from IAB translations
56
+ * **Description** — Includes the partner count (e.g., "We and our \{partnerCount} partners...")
57
+ * **Partners link** — Clickable link that opens the vendor tab in the preference center
58
+ * **Purpose/stack list** — Up to 5 purpose/stack names summarizing data usage, with an "and X more" overflow
59
+ * **Legitimate interest notice** — Required IAB disclosure about legitimate interest processing
60
+ * **Scope notice** — Service-specific scope disclosure
61
+
62
+ ## Buttons
63
+
64
+ The banner includes three action buttons:
65
+
66
+ |Button|Action|
67
+ |--|--|
68
+ |**Reject All**|Rejects all IAB purposes and closes the banner|
69
+ |**Accept All**|Accepts all IAB purposes and closes the banner|
70
+ |**Customize**|Opens the IABConsentDialog purposes tab|
71
+
72
+ ### Primary Button
73
+
74
+ Highlight a specific button as the primary action:
75
+
76
+ ```tsx
77
+ <IABConsentBanner primaryButton="accept" />
78
+ ```
79
+
80
+ Options: `'reject'`, `'accept'`, `'customize'` (default: `'customize'`)
81
+
82
+ ## Props
83
+
84
+ ### IABConsentBannerProps
85
+
86
+ |Property|Type|Description|Default|Required|
87
+ |:--|:--|:--|:--|:--:|
88
+ |noStyle|boolean \|undefined|When true, removes all default styling from the component.|false|Optional|
89
+ |disableAnimation|boolean \|undefined|When true, disables entrance/exit animations.|false|Optional|
90
+ |scrollLock|boolean \|undefined|When true, locks page scroll when the banner is visible.|true|Optional|
91
+ |trapFocus|boolean \|undefined|When true, traps keyboard focus within the banner.|true|Optional|
92
+ |primaryButton|"reject" \|"accept" \|"customize" \|undefined|Specifies which button should be highlighted as primary.|'customize'|Optional|
93
+ |models|Model \|undefined|Which consent models this banner responds to.|\['iab']|Optional|
94
+ |uiSource|string \|undefined|Override the UI source identifier sent with consent API calls.|'iab\_banner'|Optional|
@@ -0,0 +1,134 @@
1
+ ---
2
+ title: IABConsentDialog
3
+ description: An IAB TCF 2.3 compliant preference center with tabbed purpose and vendor management.
4
+ ---
5
+ > ❌ **Error:**
6
+ > c15t is not yet IAB certified. The IAB TCF components are under active development and should not be used in production. APIs and behavior may change before certification is achieved.
7
+
8
+ `IABConsentDialog` is an IAB TCF 2.3 compliant consent dialog that provides a tabbed interface for managing purpose consent and vendor preferences. It includes purpose grouping via stacks, individual purpose/vendor toggles, special purpose and feature disclosures, and legitimate interest handling.
9
+
10
+ ## Basic Usage
11
+
12
+ Pair it with `IABConsentBanner` inside the provider:
13
+
14
+ ```tsx
15
+ import { type ReactNode } from 'react';
16
+ import { iab } from '@c15t/iab';
17
+ import { ConsentManagerProvider } from '@c15t/nextjs';
18
+ import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
19
+
20
+ export default function ConsentManager({ children }: { children: ReactNode }) {
21
+ return (
22
+ <ConsentManagerProvider
23
+ options={{
24
+ mode: 'hosted',
25
+ backendURL: '/api/c15t',
26
+ iab: iab({
27
+ vendors: [1, 2, 10, 25],
28
+ // cmpId is automatically provided by the backend when using consent.io.
29
+ // Only set this if you have your own CMP registration.
30
+ // cmpId: 123,
31
+ }),
32
+ }}
33
+ >
34
+ <IABConsentBanner />
35
+ <IABConsentDialog />
36
+ {children}
37
+ </ConsentManagerProvider>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Tabs
43
+
44
+ The dialog has two tabs:
45
+
46
+ ### Purposes Tab
47
+
48
+ Displays all IAB purposes grouped into:
49
+
50
+ * **Standalone purposes** — Purpose 1 (Store and/or access information on a device) is always shown standalone per IAB TCF spec
51
+ * **Stacks** — Groups of related purposes determined by the GVL. Each stack is expandable to show individual purpose toggles
52
+ * **Special features** — Opt-in features like precise geolocation
53
+ * **Essential functions** — Special purposes and features that are locked (no user toggle) because they're required for basic operation
54
+
55
+ Each purpose shows:
56
+
57
+ * Name and description
58
+ * Number of vendors using this purpose
59
+ * Consent toggle (or lock icon for essential functions)
60
+ * Legitimate interest toggle where applicable
61
+ * Expandable vendor list
62
+
63
+ ### Vendors Tab
64
+
65
+ Displays all vendors from the GVL plus any custom vendors:
66
+
67
+ * Search and filter vendors
68
+ * Per-vendor consent and legitimate interest toggles
69
+ * Vendor details: privacy policy link, cookie usage, data retention
70
+ * Purpose and feature associations
71
+
72
+ ## Controlled State
73
+
74
+ By default, the dialog follows `activeUI === 'dialog'` from the consent store. Use `open` for manual control:
75
+
76
+ ```tsx
77
+ import { useState } from 'react';
78
+ import { IABConsentDialog } from '@c15t/react/iab';
79
+
80
+ function SettingsPage() {
81
+ const [open, setOpen] = useState(false);
82
+
83
+ return (
84
+ <>
85
+ <button onClick={() => setOpen(true)}>TCF Preferences</button>
86
+ <IABConsentDialog open={open} />
87
+ </>
88
+ );
89
+ }
90
+ ```
91
+
92
+ ## Floating Trigger
93
+
94
+ Add a floating button so users can re-open the dialog after dismissing the banner. IAB TCF requires the preference center to be easily resurfaceable:
95
+
96
+ ```tsx
97
+ <IABConsentDialog showTrigger />
98
+
99
+ {/* Custom trigger options */}
100
+ <IABConsentDialog
101
+ showTrigger={{
102
+ icon: 'settings',
103
+ defaultPosition: 'bottom-left',
104
+ showWhen: 'after-consent',
105
+ size: 'sm',
106
+ }}
107
+ />
108
+ ```
109
+
110
+ ## Footer Actions
111
+
112
+ The dialog footer provides three buttons:
113
+
114
+ |Button|Action|
115
+ |--|--|
116
+ |**Reject All**|Rejects all purposes and vendors, closes dialog and banner|
117
+ |**Accept All**|Accepts all purposes and vendors, closes dialog and banner|
118
+ |**Save Settings**|Saves current selections, closes dialog and banner|
119
+
120
+ ## Props
121
+
122
+ ### IABConsentDialogProps
123
+
124
+ |Property|Type|Description|Default|Required|
125
+ |:--|:--|:--|:--|:--:|
126
+ |open|boolean \|undefined|Control the open state. If omitted, follows activeUI === 'dialog' from context.|-|Optional|
127
+ |noStyle|boolean \|undefined|When true, removes all default styling.|false|Optional|
128
+ |disableAnimation|boolean \|undefined|When true, disables entrance/exit animations.|false|Optional|
129
+ |scrollLock|boolean \|undefined|When true, locks page scroll when the dialog is visible.|true|Optional|
130
+ |trapFocus|boolean \|undefined|When true, traps keyboard focus within the dialog.|true|Optional|
131
+ |hideBranding|boolean \|undefined|When true, hides the branding in the footer.|false|Optional|
132
+ |showTrigger|ConsentDialogTriggerProps \|undefined|Show a floating trigger button to resurface the consent dialog. IAB TCF requires the consent dialog to be easily resurfaceable. Options: \`true\` - Show trigger with default settings; \`false\` - Hide trigger (default); \`ConsentDialogTriggerProps\` - Show trigger with custom props|false|Optional|
133
+ |models|Model \|undefined|Which consent models this dialog responds to.|\['iab']|Optional|
134
+ |uiSource|string \|undefined|Override the UI source identifier sent with consent API calls.|'iab\_dialog'|Optional|