@c15t/nextjs 2.0.0-rc.4 → 2.0.0-rc.6

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 (87) hide show
  1. package/dist/headless.cjs +1 -1
  2. package/dist/iab/styles.css +1 -0
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/libs/browser-initial-data.cjs +1 -0
  6. package/dist/libs/browser-initial-data.js +1 -0
  7. package/dist/libs/initial-data.cjs +1 -1
  8. package/dist/libs/initial-data.js +1 -1
  9. package/dist/styles.css +1 -0
  10. package/dist/types.cjs +1 -1
  11. package/dist/version.cjs +1 -1
  12. package/dist/version.js +1 -1
  13. package/dist-types/headless.d.ts +1 -0
  14. package/{dist → dist-types}/index.d.ts +4 -3
  15. package/dist-types/libs/browser-initial-data.d.ts +9 -0
  16. package/{dist → dist-types}/libs/initial-data.d.ts +1 -2
  17. package/dist-types/types.d.ts +38 -0
  18. package/dist-types/version.d.ts +1 -0
  19. package/docs/README.md +73 -0
  20. package/docs/building-headless-components.md +250 -0
  21. package/docs/callbacks.md +117 -0
  22. package/docs/components/consent-banner.md +174 -0
  23. package/docs/components/consent-dialog-link.md +59 -0
  24. package/docs/components/consent-dialog-trigger.md +103 -0
  25. package/docs/components/consent-dialog.md +137 -0
  26. package/docs/components/consent-manager-provider.md +423 -0
  27. package/docs/components/consent-widget.md +78 -0
  28. package/docs/components/dev-tools.md +63 -0
  29. package/docs/components/frame.md +73 -0
  30. package/docs/concepts/client-modes.md +163 -0
  31. package/docs/concepts/consent-categories.md +97 -0
  32. package/docs/concepts/consent-models.md +116 -0
  33. package/docs/concepts/cookie-management.md +122 -0
  34. package/docs/concepts/glossary.md +23 -0
  35. package/docs/concepts/initialization-flow.md +141 -0
  36. package/docs/concepts/policy-packs.md +229 -0
  37. package/docs/headless.md +184 -0
  38. package/docs/hooks/use-color-scheme.md +40 -0
  39. package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
  40. package/docs/hooks/use-consent-manager/location-info.md +95 -0
  41. package/docs/hooks/use-consent-manager/overview.md +390 -0
  42. package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
  43. package/docs/hooks/use-draggable.md +57 -0
  44. package/docs/hooks/use-focus-trap.md +41 -0
  45. package/docs/hooks/use-reduced-motion.md +35 -0
  46. package/docs/hooks/use-ssr-status.md +31 -0
  47. package/docs/hooks/use-text-direction.md +49 -0
  48. package/docs/hooks/use-translations.md +117 -0
  49. package/docs/iab/consent-banner.md +95 -0
  50. package/docs/iab/consent-dialog.md +135 -0
  51. package/docs/iab/overview.md +119 -0
  52. package/docs/iab/use-gvl-data.md +208 -0
  53. package/docs/iframe-blocking.md +107 -0
  54. package/docs/integrations/databuddy.md +186 -0
  55. package/docs/integrations/google-tag-manager.md +153 -0
  56. package/docs/integrations/google-tag.md +149 -0
  57. package/docs/integrations/linkedin-insights.md +109 -0
  58. package/docs/integrations/meta-pixel.md +342 -0
  59. package/docs/integrations/microsoft-uet.md +112 -0
  60. package/docs/integrations/overview.md +89 -0
  61. package/docs/integrations/posthog.md +177 -0
  62. package/docs/integrations/tiktok-pixel.md +113 -0
  63. package/docs/integrations/x-pixel.md +143 -0
  64. package/docs/internationalization.md +197 -0
  65. package/docs/network-blocker.md +178 -0
  66. package/docs/optimization.md +156 -0
  67. package/docs/policy-packs.md +246 -0
  68. package/docs/quickstart.md +155 -0
  69. package/docs/script-loader.md +300 -0
  70. package/docs/server-side.md +171 -0
  71. package/docs/styling/classnames.md +84 -0
  72. package/docs/styling/color-scheme.md +82 -0
  73. package/docs/styling/css-variables.md +92 -0
  74. package/docs/styling/overview.md +312 -0
  75. package/docs/styling/slots.md +93 -0
  76. package/docs/styling/tailwind.md +115 -0
  77. package/docs/styling/tokens.md +214 -0
  78. package/docs/troubleshooting.md +146 -0
  79. package/package.json +20 -13
  80. package/dist/headless.d.ts +0 -2
  81. package/dist/headless.d.ts.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/libs/initial-data.d.ts.map +0 -1
  84. package/dist/types.d.ts +0 -16
  85. package/dist/types.d.ts.map +0 -1
  86. package/dist/version.d.ts +0 -2
  87. package/dist/version.d.ts.map +0 -1
@@ -0,0 +1,171 @@
1
+ ---
2
+ title: Server-Side Data Fetching
3
+ description: Pre-fetch consent data in Server Components with fetchInitialData — eliminate the loading flash and enable streaming.
4
+ ---
5
+ The `@c15t/nextjs` package provides `fetchInitialData`, a server-side function that fetches consent data before rendering. This enables SSR hydration — consent state is available immediately on page load without a client-side fetch, eliminating the consent banner flash.
6
+
7
+ > ℹ️ **Info:**
8
+ > SSR hydration is part of the initialization flow. When SSR data is available, the client skips the API fetch entirely.
9
+
10
+ ```ts
11
+ import { fetchInitialData } from '@c15t/nextjs';
12
+ ```
13
+
14
+ ## fetchInitialData
15
+
16
+ The primary function for fetching consent data on the server. It calls the c15t backend's `/init` endpoint with the user's request headers (resolved automatically from `next/headers`) and returns the initial consent data.
17
+
18
+ ```ts title="app/layout.tsx"
19
+ import { fetchInitialData } from '@c15t/nextjs';
20
+
21
+ // In a Server Component — do NOT await
22
+ const ssrData = fetchInitialData({
23
+ backendURL: '/api/c15t',
24
+ debug: process.env.NODE_ENV === 'development',
25
+ });
26
+ ```
27
+
28
+ > ℹ️ **Info:**
29
+ > Do not await fetchInitialData() in Server Components. Pass the Promise directly to your client component so Next.js can stream the page while the consent data loads in parallel.
30
+ >
31
+ > ℹ️ **Info:**
32
+ > Need fully static routes? Use C15tPrefetch in your layout and getPrefetchedInitialData() in your client provider instead of fetchInitialData(). See Optimization.
33
+
34
+ ### How Streaming Works
35
+
36
+ The diagram below shows how the prefetch avoids blocking the page render. The server fires the `/init` request and immediately starts streaming HTML — the resolved consent data is sent as a later chunk once the backend responds.
37
+
38
+ ```mermaid
39
+ sequenceDiagram
40
+ participant SC as Server Component
41
+ participant NR as Next.js Runtime
42
+ participant BR as Browser
43
+ participant BE as c15t Backend
44
+
45
+ SC->>BE: fetchInitialData() → GET /init
46
+ Note right of SC: Returns a Promise - (not awaited)
47
+ SC->>NR: Render page tree - (Promise passed as prop)
48
+ NR->>BR: Stream initial HTML
49
+ BR->>BR: Hydrate — no consent data yet
50
+
51
+ BE-->>NR: /init response resolves
52
+ NR->>BR: Stream resolved data chunk
53
+ BR->>BR: Provider receives SSR data - skips client-side /init fetch
54
+ ```
55
+
56
+ ### Options
57
+
58
+ |Property|Type|Description|Default|Required|
59
+ |:--|:--|:--|:--|:--:|
60
+ |nextCache|NextCacheOptions \|undefined|Optional Next.js cache controls for SSR init requests.|-|Optional|
61
+
62
+ #### `nextCache` NextCacheOptions
63
+
64
+ Optional Next.js cache controls for SSR init requests.
65
+
66
+ |Property|Type|Description|Default|Required|
67
+ |:--|:--|:--|:--|:--:|
68
+ |revalidateSeconds|number \|false \|undefined|Cache lifetime in seconds for the Next.js data cache. Set to false to disable Next.js caching for this call.|-|Optional|
69
+
70
+ ### Return Value
71
+
72
+ Returns `Promise<SSRInitialData | undefined>`. The data includes the init response (jurisdiction, translations, consent model) and GVL data when IAB is configured.
73
+
74
+ ### Passing SSR Data to the Provider
75
+
76
+ Pass the unresolved Promise to the provider's `ssrData` option via a client component:
77
+
78
+ ```tsx title="components/consent-manager/index.tsx"
79
+ 'use client';
80
+
81
+ import { type ReactNode } from 'react';
82
+ import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';
83
+ import type { InitialDataPromise } from '@c15t/nextjs';
84
+
85
+ export default function ConsentManager({
86
+ children,
87
+ ssrData,
88
+ }: {
89
+ children: ReactNode;
90
+ ssrData?: InitialDataPromise;
91
+ }) {
92
+ return (
93
+ <ConsentManagerProvider
94
+ options={{
95
+ mode: 'hosted',
96
+ backendURL: '/api/c15t',
97
+ store: { ssrData },
98
+ }}
99
+ >
100
+ <ConsentBanner />
101
+ <ConsentDialog />
102
+ {children}
103
+ </ConsentManagerProvider>
104
+ );
105
+ }
106
+ ```
107
+
108
+ ```tsx title="app/layout.tsx"
109
+ import { fetchInitialData } from '@c15t/nextjs';
110
+ import ConsentManager from '@/components/consent-manager';
111
+
112
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
113
+ const ssrData = fetchInitialData({
114
+ backendURL: '/api/c15t',
115
+ });
116
+
117
+ return (
118
+ <html lang="en">
119
+ <body>
120
+ <ConsentManager ssrData={ssrData}>
121
+ {children}
122
+ </ConsentManager>
123
+ </body>
124
+ </html>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ## How Headers Are Resolved
130
+
131
+ Unlike `@c15t/react/server` where you must pass headers manually, `fetchInitialData` uses `next/headers` to automatically resolve the incoming request headers. This means geo-location headers from Vercel, Cloudflare, or AWS CloudFront are forwarded to the c15t backend without any extra configuration.
132
+
133
+ |Header|Source|Contains|
134
+ |--|--|--|
135
+ |`cf-ipcountry`|Cloudflare|Country code|
136
+ |`x-vercel-ip-country`|Vercel|Country code|
137
+ |`x-amz-cf-ipcountry`|AWS CloudFront|Country code|
138
+ |`x-vercel-ip-country-region`|Vercel|Region code|
139
+ |`accept-language`|Browser|Language preference|
140
+ |`x-forwarded-host`|Proxy|Original host|
141
+ |`x-forwarded-for`|Proxy|Client IP|
142
+
143
+ ## Advanced: Using @c15t/react/server Directly
144
+
145
+ For advanced use cases (custom server frameworks, edge functions, or non-standard header resolution), the underlying utilities from `@c15t/react/server` are available:
146
+
147
+ ```ts
148
+ import {
149
+ fetchSSRData,
150
+ extractRelevantHeaders,
151
+ normalizeBackendURL,
152
+ validateBackendURL,
153
+ } from '@c15t/react/server';
154
+ ```
155
+
156
+ See the [React Server-Side Utilities](/docs/frameworks/react/server-side) docs for full API details.
157
+
158
+ ## Debugging SSR
159
+
160
+ Use the [useSSRStatus](/docs/frameworks/next/hooks/use-ssr-status) hook on the client to verify SSR data was consumed:
161
+
162
+ ```tsx
163
+ import { useSSRStatus } from '@c15t/nextjs';
164
+
165
+ function DebugSSR() {
166
+ const { ssrDataUsed, ssrSkippedReason } = useSSRStatus();
167
+
168
+ if (ssrDataUsed) return <span>SSR hydration successful</span>;
169
+ return <span>SSR skipped: {ssrSkippedReason ?? 'unknown'}</span>;
170
+ }
171
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: Class Names
3
+ description: Style consent components using className props and per-slot className targeting via the theme.
4
+ ---
5
+ ## Component className
6
+
7
+ All consent components accept a `className` prop:
8
+
9
+ ```tsx
10
+ <ConsentBanner className="my-banner" />
11
+ <ConsentDialog className="my-dialog" />
12
+ <ConsentWidget className="my-widget" />
13
+ ```
14
+
15
+ ## Per-Slot className
16
+
17
+ Target individual component parts via the theme's `slots` object. Each slot accepts a string (className) or an object with `className` and `style`:
18
+
19
+ ```tsx
20
+ const theme = {
21
+ slots: {
22
+ consentBannerTitle: 'text-xl font-semibold',
23
+ consentBannerDescription: 'text-sm text-gray-600',
24
+ consentBannerFooter: 'flex gap-3',
25
+ buttonPrimary: 'rounded-full px-6',
26
+ },
27
+ } satisfies Theme;
28
+ ```
29
+
30
+ ## Combining with CSS Modules
31
+
32
+ ```tsx
33
+ import styles from './consent.module.css';
34
+
35
+ const theme = {
36
+ slots: {
37
+ consentBannerCard: styles.bannerCard,
38
+ consentBannerTitle: styles.bannerTitle,
39
+ buttonPrimary: styles.primaryButton,
40
+ },
41
+ } satisfies Theme;
42
+ ```
43
+
44
+ ```css title="consent.module.css"
45
+ .bannerCard {
46
+ backdrop-filter: blur(12px);
47
+ background: rgba(255, 255, 255, 0.9);
48
+ }
49
+
50
+ .bannerTitle {
51
+ font-size: 1.25rem;
52
+ font-weight: 700;
53
+ }
54
+
55
+ .primaryButton {
56
+ border-radius: 9999px;
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.05em;
59
+ }
60
+ ```
61
+
62
+ ## noStyle Mode
63
+
64
+ Use `noStyle` when you want to remove defaults and style from scratch:
65
+
66
+ ```tsx
67
+ {/* Remove all styles from a specific component */}
68
+ <ConsentBanner noStyle />
69
+
70
+ {/* Remove all styles globally */}
71
+ <ConsentManagerProvider options={{ noStyle: true, ... }}>
72
+ ```
73
+
74
+ You can also set `noStyle` per-slot:
75
+
76
+ ```tsx
77
+ const theme = {
78
+ slots: {
79
+ consentBannerCard: { noStyle: true, className: 'my-custom-card' },
80
+ },
81
+ } satisfies Theme;
82
+ ```
83
+
84
+ For full custom markup and behavior, continue to [Headless Mode](../headless).
@@ -0,0 +1,82 @@
1
+ ---
2
+ title: Color Scheme
3
+ description: Support light mode, dark mode, and system preference detection in consent components.
4
+ ---
5
+ c15t supports light and dark mode through the theme's `colors` and `dark` token groups. The active color scheme is determined by one of three methods:
6
+
7
+ 1. **Explicit setting** via the `colorScheme` option
8
+ 2. **CSS class detection** - c15t checks for `.dark` on the document element
9
+ 3. **System preference** - matches `prefers-color-scheme` media query
10
+
11
+ ## Configuration
12
+
13
+ Set the color scheme on the provider:
14
+
15
+ ```tsx
16
+ import { type ReactNode } from 'react';
17
+ import { ConsentManagerProvider } from '@c15t/nextjs';
18
+
19
+ function ConsentManager({ children }: { children: ReactNode }) {
20
+ return (
21
+ <ConsentManagerProvider
22
+ options={{
23
+ mode: 'hosted',
24
+ backendURL: '/api/c15t',
25
+ colorScheme: 'system', // 'light' | 'dark' | 'system'
26
+ theme: {
27
+ colors: {
28
+ primary: '#6366f1',
29
+ surface: '#ffffff',
30
+ text: '#1f2937',
31
+ },
32
+ dark: {
33
+ primary: '#818cf8',
34
+ surface: '#1f2937',
35
+ text: '#f9fafb',
36
+ },
37
+ },
38
+ }}
39
+ >
40
+ {children}
41
+ </ConsentManagerProvider>
42
+ );
43
+ }
44
+ ```
45
+
46
+ ## useColorScheme Hook
47
+
48
+ For programmatic control over the color scheme:
49
+
50
+ ```tsx
51
+ import { useColorScheme } from '@c15t/nextjs';
52
+
53
+ function ThemeToggle() {
54
+ // Pass the desired scheme - 'light', 'dark', 'system', or null (disable)
55
+ useColorScheme('system');
56
+ }
57
+ ```
58
+
59
+ |Value|Behavior|
60
+ |--|--|
61
+ |`'light'`|Force light mode|
62
+ |`'dark'`|Force dark mode|
63
+ |`'system'`|Follow `prefers-color-scheme` media query|
64
+ |`null`|Disable - c15t won't manage color scheme|
65
+
66
+ ## How Dark Mode Works
67
+
68
+ When dark mode is active, c15t applies the `dark` token values as CSS variable overrides. Only tokens specified in `dark` are overridden - unset tokens fall back to the `colors` values.
69
+
70
+ ```tsx
71
+ const theme = {
72
+ colors: {
73
+ surface: '#ffffff', // Light mode
74
+ text: '#1f2937', // Light mode
75
+ },
76
+ dark: {
77
+ surface: '#1f2937', // Dark mode override
78
+ text: '#f9fafb', // Dark mode override
79
+ // primary is NOT set - inherits from colors.primary
80
+ },
81
+ } satisfies Theme;
82
+ ```
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: CSS Variables
3
+ description: Reference for all --c15t-* CSS custom properties generated by the theme system.
4
+ ---
5
+ Every theme token is converted to a `--c15t-*` CSS custom property at runtime. You can override these variables in your stylesheet without using the JavaScript theme API.
6
+
7
+ ## Variable Reference
8
+
9
+ ### ThemeCSSVariables
10
+
11
+ |Property|Type|Description|Default|Required|
12
+ |:--|:--|:--|:--|:--:|
13
+ |--c15t-primary|string \|undefined|\`colors.primary\` (default: \`hsl(228, 100%, 60%)\`)|-|Optional|
14
+ |--c15t-primary-hover|string \|undefined|\`colors.primaryHover\` (default: \`hsl(228, 100%, 55%)\`)|-|Optional|
15
+ |--c15t-surface|string \|undefined|\`colors.surface\` (default: \`hsl(0, 0%, 100%)\`)|-|Optional|
16
+ |--c15t-surface-hover|string \|undefined|\`colors.surfaceHover\` (default: \`hsl(0, 0%, 98%)\`)|-|Optional|
17
+ |--c15t-border|string \|undefined|\`colors.border\` (default: \`hsl(0, 0%, 90%)\`)|-|Optional|
18
+ |--c15t-border-hover|string \|undefined|\`colors.borderHover\` (default: \`hsl(0, 0%, 85%)\`)|-|Optional|
19
+ |--c15t-text|string \|undefined|\`colors.text\` (default: \`hsl(0, 0%, 10%)\`)|-|Optional|
20
+ |--c15t-text-muted|string \|undefined|\`colors.textMuted\` (default: \`hsl(0, 0%, 40%)\`)|-|Optional|
21
+ |--c15t-text-on-primary|string \|undefined|\`colors.textOnPrimary\` (default: \`hsl(0, 0%, 100%)\`)|-|Optional|
22
+ |--c15t-overlay|string \|undefined|\`colors.overlay\` (default: \`hsla(0, 0%, 0%, 0.5)\`)|-|Optional|
23
+ |--c15t-switch-track|string \|undefined|\`colors.switchTrack\` (default: \`hsl(0, 0%, 85%)\`)|-|Optional|
24
+ |--c15t-switch-track-active|string \|undefined|\`colors.switchTrackActive\` (default: \`hsl(228, 100%, 60%)\`)|-|Optional|
25
+ |--c15t-switch-thumb|string \|undefined|\`colors.switchThumb\` (default: \`hsl(0, 0%, 100%)\`)|-|Optional|
26
+ |--c15t-font-family|string \|undefined|\`typography.fontFamily\` (default: \`system-ui, -apple-system, sans-serif\`)|-|Optional|
27
+ |--c15t-font-size-sm|string \|undefined|\`typography.fontSize.sm\` (default: \`0.875rem\`)|-|Optional|
28
+ |--c15t-font-size-base|string \|undefined|\`typography.fontSize.base\` (default: \`1rem\`)|-|Optional|
29
+ |--c15t-font-size-lg|string \|undefined|\`typography.fontSize.lg\` (default: \`1.125rem\`)|-|Optional|
30
+ |--c15t-font-weight-normal|string \|undefined|\`typography.fontWeight.normal\` (default: \`400\`)|-|Optional|
31
+ |--c15t-font-weight-medium|string \|undefined|\`typography.fontWeight.medium\` (default: \`500\`)|-|Optional|
32
+ |--c15t-font-weight-semibold|string \|undefined|\`typography.fontWeight.semibold\` (default: \`600\`)|-|Optional|
33
+ |--c15t-line-height-tight|string \|undefined|\`typography.lineHeight.tight\` (default: \`1.25\`)|-|Optional|
34
+ |--c15t-line-height-normal|string \|undefined|\`typography.lineHeight.normal\` (default: \`1.5\`)|-|Optional|
35
+ |--c15t-line-height-relaxed|string \|undefined|\`typography.lineHeight.relaxed\` (default: \`1.75\`)|-|Optional|
36
+ |--c15t-space-xs|string \|undefined|\`spacing.xs\` (default: \`0.25rem\`)|-|Optional|
37
+ |--c15t-space-sm|string \|undefined|\`spacing.sm\` (default: \`0.5rem\`)|-|Optional|
38
+ |--c15t-space-md|string \|undefined|\`spacing.md\` (default: \`1rem\`)|-|Optional|
39
+ |--c15t-space-lg|string \|undefined|\`spacing.lg\` (default: \`1.5rem\`)|-|Optional|
40
+ |--c15t-space-xl|string \|undefined|\`spacing.xl\` (default: \`2rem\`)|-|Optional|
41
+ |--c15t-radius-sm|string \|undefined|\`radius.sm\` (default: \`0.25rem\`)|-|Optional|
42
+ |--c15t-radius-md|string \|undefined|\`radius.md\` (default: \`0.5rem\`)|-|Optional|
43
+ |--c15t-radius-lg|string \|undefined|\`radius.lg\` (default: \`0.75rem\`)|-|Optional|
44
+ |--c15t-radius-full|string \|undefined|\`radius.full\` (default: \`9999px\`)|-|Optional|
45
+ |--c15t-shadow-sm|string \|undefined|\`shadows.sm\` (default: \`0 1px 2px hsla(0, 0%, 0%, 0.05)\`)|-|Optional|
46
+ |--c15t-shadow-md|string \|undefined|\`shadows.md\` (default: \`0 4px 12px hsla(0, 0%, 0%, 0.08)\`)|-|Optional|
47
+ |--c15t-shadow-lg|string \|undefined|\`shadows.lg\` (default: \`0 8px 24px hsla(0, 0%, 0%, 0.12)\`)|-|Optional|
48
+ |--c15t-duration-fast|string \|undefined|\`motion.duration.fast\` (default: \`100ms\`)|-|Optional|
49
+ |--c15t-duration-normal|string \|undefined|\`motion.duration.normal\` (default: \`200ms\`)|-|Optional|
50
+ |--c15t-duration-slow|string \|undefined|\`motion.duration.slow\` (default: \`300ms\`)|-|Optional|
51
+ |--c15t-easing|string \|undefined|\`motion.easing\` (default: \`cubic-bezier(0.4, 0, 0.2, 1)\`)|-|Optional|
52
+ |--c15t-easing-out|string \|undefined|\`motion.easingOut\` (default: \`cubic-bezier(0.215, 0.61, 0.355, 1)\`)|-|Optional|
53
+ |--c15t-easing-in-out|string \|undefined|\`motion.easingInOut\` (default: \`cubic-bezier(0.645, 0.045, 0.355, 1)\`)|-|Optional|
54
+ |--c15t-easing-spring|string \|undefined|\`motion.easingSpring\` (default: \`cubic-bezier(0.34, 1.56, 0.64, 1)\`)|-|Optional|
55
+
56
+ ## Overriding Variables
57
+
58
+ Override in your stylesheet:
59
+
60
+ ```css
61
+ :root {
62
+ --c15t-primary: #8b5cf6;
63
+ --c15t-surface: #fafafa;
64
+ --c15t-radius-md: 1rem;
65
+ }
66
+
67
+ /* Dark mode overrides */
68
+ .dark,
69
+ .c15t-dark {
70
+ --c15t-primary: #a78bfa;
71
+ --c15t-surface: #18181b;
72
+ --c15t-text: #fafafa;
73
+ }
74
+ ```
75
+
76
+ ## Scoped Overrides
77
+
78
+ Target specific components by scoping variables:
79
+
80
+ ```tsx
81
+ <div className="checkout-consent">
82
+ <ConsentBanner />
83
+ </div>
84
+ ```
85
+
86
+ ```css
87
+ /* Only affect c15t components inside .checkout-consent */
88
+ .checkout-consent {
89
+ --c15t-surface: #f0f9ff;
90
+ --c15t-radius-lg: 1.5rem;
91
+ }
92
+ ```