@c15t/react 3.0.0-alpha.8 → 3.0.0-alpha.9

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 (53) hide show
  1. package/AGENTS.md +3 -3
  2. package/dist/components/consent-gate/atoms.js +1 -1
  3. package/dist/components/iab-panel/atoms/root.js +1 -1
  4. package/dist/components/iab-panel/iab-panel.js +1 -1
  5. package/dist/components/iab-prompt/atoms/root.js +1 -1
  6. package/dist/components/panel/atoms/root.js +1 -1
  7. package/dist/components/panel-trigger/atoms/root.js +1 -1
  8. package/dist/components/preferences/atoms/root.js +1 -1
  9. package/dist/components/prompt/atoms/root.js +1 -1
  10. package/dist/components/prompt/prompt.js +1 -1
  11. package/dist/components/shared/dialog-sheets.js +1 -0
  12. package/dist/components/shared/first-paint-sheets.js +1 -0
  13. package/dist/components/shared/iab-dialog-sheets.js +1 -0
  14. package/dist/components/shared/iab-first-paint-sheets.js +1 -0
  15. package/dist/components/shared/surface-styles.js +2 -0
  16. package/dist/hooks/use-iab-policy-guard.js +1 -0
  17. package/dist/iab/styles.tw3.css +1 -1
  18. package/dist/iab-context.js +1 -1
  19. package/dist/provider.js +1 -1
  20. package/dist/version.js +1 -1
  21. package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
  22. package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
  23. package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
  24. package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
  25. package/dist-types/components/shared/surface-styles.d.ts +36 -0
  26. package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
  27. package/dist-types/provider.d.ts +19 -1
  28. package/dist-types/ui-config-context.d.ts +4 -0
  29. package/dist-types/version.d.ts +1 -1
  30. package/docs/README.md +3 -3
  31. package/docs/concepts/choose-your-setup.md +3 -1
  32. package/docs/concepts/data-fetching.md +3 -1
  33. package/docs/customization/overview.md +3 -2
  34. package/docs/customization/stylesheets.md +163 -50
  35. package/docs/customization/tailwind.md +59 -44
  36. package/docs/customization/tokens.md +6 -6
  37. package/docs/frameworks/react/components/consent-banner.md +0 -2
  38. package/docs/frameworks/react/components/consent-dialog.md +1 -3
  39. package/docs/frameworks/react/components/consent-provider.md +0 -2
  40. package/docs/frameworks/react/components.md +2 -3
  41. package/docs/frameworks/react/compose.md +3 -3
  42. package/docs/frameworks/react/content-security-policy.md +22 -8
  43. package/docs/frameworks/react/customize.md +21 -13
  44. package/docs/frameworks/react/iab.md +36 -4
  45. package/docs/frameworks/react/quickstart.md +5 -5
  46. package/docs/frameworks/react/rendering.md +5 -3
  47. package/docs/frameworks/react/scripts.md +0 -2
  48. package/docs/frameworks/react/troubleshooting.md +43 -7
  49. package/docs/frameworks/react/upgrade-v3.md +23 -17
  50. package/docs/frameworks/react/vendor-consent.md +19 -7
  51. package/docs/guides/banner-experiments.md +0 -2
  52. package/docs/guides/troubleshooting.md +9 -9
  53. package/package.json +6 -6
@@ -12,6 +12,10 @@ export interface V3UIConfigValue {
12
12
  theme?: Theme;
13
13
  legalLinks?: LegalLinks;
14
14
  preloadDialog?: DialogPreload;
15
+ /** CSP nonce for the `<style>` elements the surfaces render. */
16
+ nonce?: string;
17
+ /** `false` when the app loads `styles.css` itself. */
18
+ styles?: boolean;
15
19
  }
16
20
  export declare const V3UIConfigContext: import("react").Context<V3UIConfigValue>;
17
21
  export declare const useUIConfig: () => V3UIConfigValue;
@@ -1 +1 @@
1
- export declare const version = "3.0.0-alpha.8";
1
+ export declare const version = "3.0.0-alpha.9";
package/docs/README.md CHANGED
@@ -36,7 +36,7 @@ These docs describe c15t v3. Find your app's row in Choose your setup, then foll
36
36
  - [DevTools](./frameworks/react/components/dev-tools.md): Inspect consent state, location, loaded scripts and consent events in a React app during development with the c15t DevTools panel.
37
37
  - [Compose your own banner](./frameworks/react/compose.md): Build a React consent banner from the ConsentBanner parts in c15t/react, with your own markup and buttons, while the policy still decides which actions appear.
38
38
  - [Content Security Policy](./frameworks/react/content-security-policy.md): Run c15t in a React app under a Content Security Policy, allow the consent backend and vendor hosts, and pass a nonce to ConsentProvider and ConsentTheme.
39
- - [Customize](./frameworks/react/customize.md): Change the colors, fonts, layout, button styles and copy of the c15t banner and dialog in a React app with the stylesheet, theme tokens, provider options and slots.
39
+ - [Customize](./frameworks/react/customize.md): Change the colors, fonts, layout, button styles and copy of the c15t banner and dialog in a React app with theme tokens, provider options and slots, and load the stylesheet yourself when you need to.
40
40
  - [Embeds](./frameworks/react/embeds.md): Keep YouTube videos, maps and other iframes out of a React page until their consent category is allowed, with ConsentGate or the iframe blocker in ConsentProvider.
41
41
  - [IAB GPP](./frameworks/react/gpp.md): Add the IAB Global Privacy Platform API (__gpp) to a React app with ConsentGPP from @c15t/react/gpp, so ad tech can read US state opt-outs and the TCF EU consent string.
42
42
  - [Headless](./frameworks/react/headless.md): Build a custom consent banner in React with the c15t/react/headless hooks inside your ConsentProvider.
@@ -74,8 +74,8 @@ These docs describe c15t v3. Find your app's row in Choose your setup, then foll
74
74
  - [Customize the interface](./customization/overview.md): Change c15t's consent banner and dialog one step at a time, from a prop to your own markup, and find where each step lives in your framework.
75
75
  - [Banner designs](./customization/recipes.md): Five consent banner designs built with c15t, from one prop to your own markup, with tested code for React, Vue, Svelte, Astro and plain HTML.
76
76
  - [Component parts](./customization/slots.md): Find every part of c15t's banner, dialog, widget, trigger and ConsentGate placeholder, its key in your framework's part API, and the data attributes to select on.
77
- - [Stylesheets and CSS layers](./customization/stylesheets.md): Load the right c15t stylesheet for your framework, see which file holds the dialog's rules, order c15t's cascade layer against your own, and run c15t without its styles.
78
- - [Tailwind CSS](./customization/tailwind.md): Load c15t's styles next to Tailwind CSS 4 or 3 in every framework, put utilities on c15t component parts, and use Tailwind's dark variant with c15t.
77
+ - [Stylesheets and CSS layers](./customization/stylesheets.md): See how c15t delivers its styles in each framework without a render-blocking stylesheet, when to import styles.css yourself with styles false, which file holds the dialog's rules, and how c15t's cascade layer meets your own.
78
+ - [Tailwind CSS](./customization/tailwind.md): Use c15t's styles with Tailwind CSS 4 or 3 in every framework, put utilities on c15t component parts, and use Tailwind's dark variant with c15t.
79
79
  - [Theme tokens](./customization/tokens.md): Change c15t's colors, type, radius, spacing, shadows and motion with theme tokens, and see every --c15t-* variable with its default.
80
80
  - [Copy and translations](./customization/translations.md): Change consent wording through i18n and test the complete prompt and preferences flow.
81
81
 
@@ -88,7 +88,9 @@ The snapshot is fixed at build time:
88
88
 
89
89
  * Rebuild after changing policies, translations or vendors. If your CI caches
90
90
  build output, force a fresh build.
91
- * The build must reach the backend. A failed fetch fails the build.
91
+ * The build must reach the backend. A failed fetch stops Next.js, TanStack
92
+ Start, SvelteKit and Vite builds. By default, Astro and Nuxt log a warning
93
+ instead and fetch the policy at runtime.
92
94
  * Consent choices still go to the backend.
93
95
 
94
96
  For deployments that need policy updates without a rebuild, choose a runtime
@@ -55,7 +55,9 @@ The snapshot is fixed at build time:
55
55
 
56
56
  * Rebuild after changing policies, translations or vendors. If your CI caches
57
57
  build output, force a fresh build.
58
- * The build must reach the backend. A failed fetch fails the build.
58
+ * The build must reach the backend. A failed fetch stops Next.js, TanStack
59
+ Start, SvelteKit and Vite builds. By default, Astro and Nuxt log a warning
60
+ instead and fetch the policy at runtime.
59
61
  * Consent choices still go to the backend.
60
62
 
61
63
  ## How manifest mode counts visitors
@@ -67,8 +67,9 @@ policy requires, c15t puts it back.
67
67
  dark first paint.
68
68
  * [Motion and animation](./motion.md): duration and easing
69
69
  tokens, `disableAnimation` and reduced motion.
70
- * [Stylesheets and CSS layers](./stylesheets.md): which file to
71
- load, when the dialog's CSS loads, and how to run without c15t's styles.
70
+ * [Stylesheets and CSS layers](./stylesheets.md): how c15t
71
+ loads its rules, when to import `styles.css` yourself, when the dialog's CSS
72
+ loads, and how to run without c15t's styles.
72
73
  * [Component parts](./slots.md): every part, its keys in each
73
74
  framework, and the `data-*` attributes to select on.
74
75
  * [Class names and CSS-in-JS](./class-names.md): CSS Modules,
@@ -1,45 +1,145 @@
1
1
  ---
2
2
  title: Stylesheets and CSS layers
3
- description: Load the right c15t stylesheet for your framework, see which file
4
- holds the dialog's rules, order c15t's cascade layer against your own, and run
5
- c15t without its styles.
3
+ description: See how c15t delivers its styles in each framework without a
4
+ render-blocking stylesheet, when to import styles.css yourself with styles
5
+ false, which file holds the dialog's rules, and how c15t's cascade layer meets
6
+ your own.
6
7
  group: customization
7
8
  ---
8
9
 
9
- ## Load one stylesheet for your framework
10
-
11
- | Framework | What to load | Where |
12
- | ----------------- | -------------------------------- | -------------------------------------------------------------------- |
13
- | Next.js | `c15t/next/styles.css` | Your global stylesheet, or the layout that renders the consent root |
14
- | TanStack Start | `c15t/tanstack-start/styles.css` | A `<link>` from the root route's `head()`, or your global stylesheet |
15
- | React | `c15t/react/styles.css` | The module that renders `ConsentProvider`, or your global stylesheet |
16
- | Nuxt, Vue | Nothing | Each Vue component imports its own stylesheet |
17
- | Astro | Nothing | The integration adds `c15t/astro/styles.css` to every page |
18
- | Svelte, SvelteKit | `@c15t/svelte/styles.css` | Your entry module or root layout |
19
- | HTML | Nothing | `c15t.js` carries its stylesheet into the UI's shadow root |
20
- | JavaScript | Nothing | `init()` carries its stylesheet into the UI's shadow root |
21
-
22
- Load it once. With IAB TCF in Next.js, TanStack Start, React, Svelte or
23
- SvelteKit, also load the adapter's `iab/styles.css`, such as
24
- `c15t/next/iab/styles.css`, after `styles.css`. Astro adds its IAB stylesheet
25
- when the integration sets `iab`. Tailwind 3 uses the same files and adds a
26
- PostCSS plugin; [Tailwind CSS](./tailwind.md#set-up-tailwind-css-3)
27
- shows the setup.
10
+ ## What each framework loads
11
+
12
+ | Framework | What you import | How the styles arrive |
13
+ | ------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
+ | Next.js, TanStack Start, React | Nothing | Each stock surface renders the rules it uses as `<style>` elements |
15
+ | Nuxt, Vue | Nothing | Each Vue component imports its own stylesheet |
16
+ | Astro | Nothing | `<ConsentScript />` inlines the banner's rules into the HTML. The client links the dialog's rules when a dialog first opens. On a Tailwind CSS 3 site, the integration adds `c15t/astro/styles.css` to every page instead |
17
+ | Svelte | Nothing | Each stock surface inserts the rules it uses into `<head>` |
18
+ | SvelteKit | Nothing | `c15tHandle` writes a server-rendered banner's rules into the HTML. The other surfaces insert theirs in the browser |
19
+ | HTML | Nothing | `c15t.js` carries its stylesheet into the UI's shadow root |
20
+ | JavaScript | Nothing | `init()` carries its stylesheet into the UI's shadow root |
21
+
22
+ Inline c15t styles avoid a stylesheet request before the page's first paint.
23
+ A linked stylesheet in `<head>` used to delay it by a round trip. With 4x
24
+ CPU throttling, 1.6 Mbps download and 150 ms RTT, a Next.js page first painted
25
+ at about 370 ms instead of 650 ms once the link was gone.
26
+
27
+ Inline rules add bytes to each server-rendered HTML response. Enable HTTP
28
+ compression on the production host to reduce their transfer cost. On repeat
29
+ document visits, a cached external stylesheet can paint sooner than sending
30
+ the inline rules again. To use that cache, keep the stylesheet imports and
31
+ set `styles: false` as described below.
32
+
33
+ Load `styles.css` yourself with Tailwind CSS 3 or a named cascade layer.
34
+ [Import the stylesheet yourself](#import-the-stylesheet-yourself)
35
+ covers those cases.
36
+
37
+ ## How the surfaces deliver their rules
38
+
39
+ c15t splits its rules into two sheets. The first-paint sheet holds the
40
+ default tokens, every c15t CSS variable, and the rules for the banner, the
41
+ floating trigger and the `ConsentGate` placeholder. The dialog sheet holds the
42
+ preference dialog's and the preference widget's rules.
43
+
44
+ In React, Next.js and TanStack Start, `ConsentBanner`, `ConsentDialogTrigger`
45
+ and the default `ConsentGate` placeholder render the first-paint sheet.
46
+ `ConsentDialog` and `ConsentWidget` render it plus the dialog sheet. The
47
+ dialog sheet ships with the dialog's lazily loaded code, so it is not part of
48
+ the first page load. A server-rendered banner puts its rules in the HTML.
49
+
50
+ `IABConsentBanner` also delivers the IAB variables and banner rules.
51
+ `IABConsentDialog` adds the IAB dialog rules when it opens, including the
52
+ shared rules it needs when rendered without a banner. IAB setups need no
53
+ stylesheet imports with automatic styling enabled.
54
+
55
+ Streamed React IAB banners become visible once their complete card markup
56
+ arrives. This prevents the centered card from moving as the browser parses
57
+ its remaining content, without waiting for hydration.
58
+
59
+ * In React 19, React moves the elements into `<head>` and renders each sheet
60
+ once per page, as `<style data-precedence="c15t" data-href="c15t-first-paint">`.
61
+ * In React 18, the elements render in place, next to the surface.
62
+ * In React 19 with the provider's `nonce` option set, they also render in
63
+ place and carry the nonce. React drops the nonce of a style it moves to
64
+ `<head>` unless the app passed that nonce to React's server renderer.
65
+
66
+ In Svelte, each stock surface inserts the sheets it uses into `<head>` as
67
+ `<style data-c15t-styles="…">`, once per page, before its own elements
68
+ render. The elements stay after the surface unmounts. The Svelte dialog's
69
+ sheets, its rules and the primitives, load with the dialog's code.
70
+
71
+ On a server-rendered SvelteKit page, the banner leaves a
72
+ `<meta name="c15t-styles">` marker, and `c15tHandle` from `@c15t/svelte/kit`
73
+ writes the sheets into the HTML `<head>` as `<style>` elements. The banner is
74
+ styled before hydration with no stylesheet request. Without `c15tHandle` in
75
+ `src/hooks.server.ts`, a server-rendered banner shows unstyled until
76
+ hydration. The [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart)
77
+ adds the handle.
78
+
79
+ In Astro, `<ConsentScript />`, or the banner on a layout without it, inlines
80
+ the first-paint sheet into the HTML as
81
+ `<style data-c15t-styles="c15t-first-paint">` with the page nonce. When
82
+ `tailwindcss` resolves to version 3 from the Astro root, the integration
83
+ inlines nothing and adds `c15t/astro/styles.css` to every page through
84
+ Astro's CSS pipeline, so the site's `c15t/postcss-tailwind3` plugin processes
85
+ it. That stylesheet still holds back the first paint.
86
+
87
+ The surfaces render no stylesheet with `noStyle`, or when the provider sets
88
+ `styles: false`.
89
+
90
+ ## Import the stylesheet yourself
91
+
92
+ Set `styles: false` in the provider options and import `styles.css` when you
93
+ need control over the file:
94
+
95
+ * **Tailwind CSS 3.** c15t's `<style>` rules sit in a native
96
+ `@layer components`, and Tailwind 3's unlayered preflight beats them. Import
97
+ `styles.css` through the `postcss-tailwind3` plugin instead, as
98
+ [Tailwind CSS](./tailwind.md#set-up-tailwind-css-3) shows.
99
+ Astro sites skip this: the integration detects Tailwind 3 and links the
100
+ stylesheet itself.
101
+ * **A named cascade layer.** Import the stylesheet into your own layer, as in
102
+ [order c15t's layer against your CSS](#order-c15ts-layer-against-your-css).
103
+ * **Cached styles across document visits.** Keep external CSS when its HTTP
104
+ cache performs better for your site's repeat visits. Compare cold and warm
105
+ visits on your production host before choosing this option.
106
+
107
+ | Framework | Option | Files to import |
108
+ | ----------------- | -------------------------------------------- | -------------------------------------------------------------------------------------- |
109
+ | Next.js | `options.styles` on `ConsentRoot` | `c15t/next/styles.css`, and `c15t/next/iab/styles.css` for IAB TCF |
110
+ | TanStack Start | `options.styles` on `ConsentRoot` | `c15t/tanstack-start/styles.css`, and `c15t/tanstack-start/iab/styles.css` for IAB TCF |
111
+ | React | `options.styles` on `ConsentProvider` | `c15t/react/styles.css`, and `c15t/react/iab/styles.css` for IAB TCF |
112
+ | Svelte, SvelteKit | `options.styles` on `ConsentManagerProvider` | `@c15t/svelte/styles.css`, and `@c15t/svelte/iab/styles.css` for IAB TCF |
113
+ | Astro | `styles` in the `c15t()` integration options | See [load Astro's stylesheets yourself](#load-astros-stylesheets-yourself) |
114
+
115
+ ```tsx title="components/consent.tsx (partial)"
116
+ <ConsentRoot
117
+ state={state}
118
+ config={consentConfig}
119
+ options={{ styles: false }}
120
+ >
121
+ ```
122
+
123
+ `styles: false` turns off only the stylesheets. Tokens from `ConsentTheme`
124
+ and slot classes still apply.
125
+
126
+ If you keep a `styles.css` import without `styles: false`, the surfaces still
127
+ look right, but the rules ship twice and the stylesheet link still holds back
128
+ the first paint. Remove the import, or add `styles: false` if you need it.
28
129
 
29
130
  ## Where the dialog's rules come from
30
131
 
31
- `styles.css` holds the default tokens, every c15t CSS variable and the rules
32
- for every stock surface: the banner, the floating trigger, the `ConsentGate`
33
- placeholder, the preference dialog and the preference widget. Only the IAB TCF
34
- surfaces need a second file, `iab/styles.css`.
132
+ | Framework | The preference dialog's rules |
133
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
+ | Next.js, TanStack Start, React | In the dialog sheet that `ConsentDialog` and `ConsentWidget` render. It ships with the dialog's lazily loaded code. With `styles: false`, in `styles.css`. No `@c15t/react` or `@c15t/ui` module imports CSS, so a Next.js app that uses only the Pages Router builds without `transpilePackages` |
135
+ | Nuxt, Vue | The dialog component imports its stylesheet, so the rules load with the dialog's code, not the banner's |
136
+ | Astro | In `@c15t/ui/styles/sheets/dialog.css`. The Svelte dialog, the default `ui`, also needs the primitives stylesheet. c15t links both when a visitor first points at, focuses or opens a control that opens the dialog, waits for them before it shows the dialog, and keeps them across `ClientRouter` navigation. On a Tailwind CSS 3 site, the dialog's rules are in `c15t/astro/styles.css`, and c15t links only the primitives stylesheet |
137
+ | Svelte, SvelteKit | In the dialog's sheets, which load with the dialog's code. With `styles: false`, in `@c15t/svelte/styles.css` |
138
+ | HTML, JavaScript | Part of the stylesheet in the shadow root, which holds only the rules the stock surfaces use |
35
139
 
36
- | Framework | The preference dialog's rules |
37
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
- | Next.js, TanStack Start, React | In `styles.css`. No `@c15t/react` or `@c15t/ui` module imports CSS, so a Next.js app that uses only the Pages Router builds without `transpilePackages` |
39
- | Nuxt, Vue | The dialog component imports its stylesheet, so the rules load with the dialog's code, not the banner's |
40
- | Astro | In `c15t/astro/styles.css`. The Svelte dialog, the default `ui`, also needs the primitives stylesheet, which c15t links when a visitor first points at, focuses or opens a control that opens the dialog, and keeps across `ClientRouter` navigation |
41
- | Svelte, SvelteKit | In `@c15t/svelte/styles.css` |
42
- | HTML, JavaScript | Part of the stylesheet in the shadow root, which holds only the rules the stock surfaces use |
140
+ `styles.css` still holds every stock surface's rules for apps that set
141
+ `styles: false`. In that manual mode, IAB TCF also needs `iab/styles.css`
142
+ after the base stylesheet.
43
143
 
44
144
  Earlier 3.0 alphas shipped the dialog's rules in `@c15t/ui/styles/dialog.css`,
45
145
  which the dialog's code imported. That file, the `@c15t/ui/styles/dialog`
@@ -49,8 +149,8 @@ imports keep resolving. Remove those imports.
49
149
  ## Order c15t's layer against your CSS
50
150
 
51
151
  c15t puts its component rules in the cascade layer `components`. Tokens,
52
- variables and keyframes stay unlayered. Each stylesheet opens with Tailwind
53
- CSS 4's layer order:
152
+ variables and keyframes stay unlayered. Each stylesheet, and each `<style>`
153
+ element the surfaces render, opens with Tailwind CSS 4's layer order:
54
154
 
55
155
  ```css
56
156
  @layer properties, theme, base, components, utilities;
@@ -71,18 +171,23 @@ What this means for your overrides:
71
171
  [theme tokens](./tokens.md).
72
172
 
73
173
  If your CSS declares its own layer order, load that statement before c15t's
74
- stylesheet. You can import c15t's stylesheet into a named layer, such as
174
+ rules. To put c15t's rules in a named layer, set `styles: false` and import
175
+ the stylesheet into that layer, such as
75
176
  `@import 'c15t/react/styles.css' layer(c15t)`. Its component rules then sit
76
177
  in `c15t.components`, so order `c15t` against your own layers.
77
178
 
78
179
  ## Load Astro's stylesheets yourself
79
180
 
80
- The Astro integration adds `c15t/astro/styles.css` to every page ahead of your
81
- own CSS. To control the order yourself, for example from a global stylesheet
82
- that names its own layers, set `styles: false` in the `c15t()` options and
83
- import the files after your layer order statement. With `styles: false`, c15t
84
- links no stylesheet when the dialog opens either. The Svelte dialog, the
85
- default `ui`, needs the primitives stylesheet next to `styles.css`:
181
+ By default the Astro integration inlines the banner's rules into each page,
182
+ including the IAB banner rules when it sets `iab`, and links the dialog's
183
+ rules when a dialog opens. On a Tailwind CSS 3 site it adds
184
+ `c15t/astro/styles.css` and, with IAB, `c15t/astro/iab/styles.css` to every
185
+ page instead of inlining the banner's rules. To load the
186
+ files yourself, for example from a global stylesheet that names its own
187
+ layers, set `styles: false` in the `c15t()` options and import them after
188
+ your layer order statement. With `styles: false`, c15t inlines no rules and
189
+ links no stylesheet when the dialog opens. The Svelte dialog, the default
190
+ `ui`, needs the primitives stylesheet next to `styles.css`:
86
191
 
87
192
  ```css title="src/styles/global.css"
88
193
  @import 'c15t/astro/styles.css';
@@ -92,9 +197,9 @@ default `ui`, needs the primitives stylesheet next to `styles.css`:
92
197
  @import 'c15t/astro/iab/styles.css';
93
198
  ```
94
199
 
95
- Keep c15t's stylesheet when you restyle the surfaces. The browser hides the
96
- Astro banner with the `hidden` attribute, and the stylesheet makes that
97
- attribute beat the banner's own `display` rule.
200
+ Keep c15t's rules when you restyle the surfaces. The browser hides the Astro
201
+ banner with the `hidden` attribute, and c15t's rules make that attribute beat
202
+ the banner's own `display` rule.
98
203
 
99
204
  ## Style the script tag's shadow root
100
205
 
@@ -129,9 +234,17 @@ selector before you reach for `noStyle`.
129
234
 
130
235
  ## Check the result
131
236
 
132
- 1. Open DevTools Network, clear site data and reload. Your framework's c15t
133
- stylesheet loads once, and no second copy appears.
134
- 2. Open the preference dialog. It is styled on its first frame, and in React,
135
- Next.js and TanStack Start no new stylesheet request appears.
136
- 3. Select a banner part and read the Styles panel. c15t's rules appear under
237
+ 1. Open DevTools Network, clear site data and reload. No c15t stylesheet
238
+ request appears before the first paint. In the HTML or the Elements panel,
239
+ `<head>` holds c15t's rules: `<style data-href="c15t-first-paint">` in
240
+ React 19, or a `<style data-c15t-styles>` element in Svelte, SvelteKit
241
+ and Astro. In React 18, or with a `nonce`, the `<style>` sits next to the
242
+ banner instead.
243
+ 2. Open the preference dialog. It is styled on its first frame. Opening it
244
+ adds the dialog's rules: a second `<style>` element in React, Next.js,
245
+ TanStack Start, Svelte and SvelteKit, or a linked
246
+ `@c15t/ui/styles/sheets/dialog.css` in Astro.
247
+ 3. With `styles: false`, your imported `styles.css` loads once, and no c15t
248
+ `<style>` element appears.
249
+ 4. Select a banner part and read the Styles panel. c15t's rules appear under
137
250
  `@layer components`, and your unlayered rules for the same property win.
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  title: Tailwind CSS
3
- description: Load c15t's styles next to Tailwind CSS 4 or 3 in every framework,
4
- put utilities on c15t component parts, and use Tailwind's dark variant with
5
- c15t.
3
+ description: Use c15t's styles with Tailwind CSS 4 or 3 in every framework, put
4
+ utilities on c15t component parts, and use Tailwind's dark variant with c15t.
6
5
  group: customization
7
6
  ---
8
7
 
@@ -21,7 +20,10 @@ directive and fails the build on a stylesheet that uses it without a matching
21
20
  `@tailwind components`. c15t's Tailwind 3 PostCSS plugin removes the layer
22
21
  wrappers from c15t's stylesheets before Tailwind 3 runs. c15t's rules then
23
22
  win by specificity, as they did in c15t v2, so a utility on a c15t part needs
24
- Tailwind's important modifier, such as `!p-2`.
23
+ Tailwind's important modifier, such as `!p-2`. The plugin cannot reach the
24
+ `<style>` elements c15t renders itself, so on Tailwind 3 you turn those off
25
+ with `styles: false` and import the stylesheet instead. The Astro integration
26
+ does this for you when it finds Tailwind 3.
25
27
 
26
28
  The setups below come from fixtures that CI builds for each framework and
27
29
  checks in Chromium. The Next.js snippets come from the Next.js 16 Turbopack
@@ -34,9 +36,11 @@ without a backend, so they use `offline()` mode or a local `backendURL`. Keep th
34
36
 
35
37
  ## Set up Tailwind CSS 4
36
38
 
37
- Install Tailwind's plugin for your build tool, add it to the build, and load
38
- c15t's stylesheet in the same CSS entry as Tailwind. Vue, Nuxt and Astro load
39
- c15t's styles for you, so their CSS entry holds only Tailwind.
39
+ Install Tailwind's plugin for your build tool and add it to the build. c15t
40
+ loads its own styles in every framework, so your CSS entry holds only
41
+ Tailwind. Do not import c15t's stylesheet next to Tailwind 4: the stock
42
+ components already render the same rules, and the import would hold back the
43
+ first paint.
40
44
 
41
45
  **Next.js**
42
46
 
@@ -46,20 +50,18 @@ Next.js runs Tailwind 4 through `@tailwindcss/postcss`:
46
50
  export default { plugins: { '@tailwindcss/postcss': {} } };
47
51
  ```
48
52
 
49
- Import c15t after Tailwind in the global stylesheet that `app/layout.tsx`
50
- or `pages/_app.tsx` loads:
53
+ Import Tailwind in the global stylesheet that `app/layout.tsx` or
54
+ `pages/_app.tsx` loads:
51
55
 
52
56
  ```css title="app/globals.css"
53
57
  @import 'tailwindcss';
54
- @import 'c15t/next/styles.css';
55
58
 
56
59
  @custom-variant dark (&:where(.dark, .dark *));
57
60
  ```
58
61
 
59
62
  **TanStack Start**
60
63
 
61
- Add `@tailwindcss/vite` before the TanStack Start plugin, then import
62
- Tailwind and c15t from the stylesheet your root route links:
64
+ Add `@tailwindcss/vite` before the TanStack Start plugin:
63
65
 
64
66
  ```ts title="vite.config.ts"
65
67
  import tailwindcss from '@tailwindcss/vite';
@@ -76,11 +78,10 @@ export default defineConfig({
76
78
  });
77
79
  ```
78
80
 
79
- Import Tailwind and c15t from the stylesheet your root route links:
81
+ Import Tailwind from the stylesheet your root route links:
80
82
 
81
83
  ```css title="src/styles.css"
82
84
  @import 'tailwindcss';
83
- @import 'c15t/tanstack-start/styles.css';
84
85
 
85
86
  @custom-variant dark (&:where(.dark, .dark *));
86
87
  ```
@@ -95,12 +96,10 @@ import { defineConfig } from 'vite';
95
96
  export default defineConfig({ plugins: [react(), tailwindcss()] });
96
97
  ```
97
98
 
98
- Import this stylesheet from your entry module in place of a separate
99
- `c15t/react/styles.css` import:
99
+ Import this stylesheet from your entry module:
100
100
 
101
101
  ```css title="src/app.css"
102
102
  @import 'tailwindcss';
103
- @import 'c15t/react/styles.css';
104
103
 
105
104
  @custom-variant dark (&:where(.dark, .dark *));
106
105
  ```
@@ -155,8 +154,9 @@ Import this stylesheet from `src/main.ts`:
155
154
 
156
155
  **Astro**
157
156
 
158
- The integration adds `c15t/astro/styles.css` to every page, so the CSS
159
- entry holds only Tailwind. Add `@tailwindcss/vite` under `vite.plugins`:
157
+ c15t inlines the banner's rules into each page and links the dialog's
158
+ when it opens, so the CSS entry holds only Tailwind. Add
159
+ `@tailwindcss/vite` under `vite.plugins`:
160
160
 
161
161
  ```js title="astro.config.mjs"
162
162
  import svelte from '@astrojs/svelte';
@@ -202,12 +202,10 @@ import { defineConfig } from 'vite';
202
202
  export default defineConfig({ plugins: [svelte(), tailwindcss()] });
203
203
  ```
204
204
 
205
- Import this stylesheet from `src/main.ts` in place of a separate
206
- `@c15t/svelte/styles.css` import:
205
+ Import this stylesheet from `src/main.ts`:
207
206
 
208
207
  ```css title="src/app.css"
209
208
  @import 'tailwindcss';
210
- @import '@c15t/svelte/styles.css';
211
209
 
212
210
  @custom-variant dark (&:where(.dark, .dark *));
213
211
  ```
@@ -226,7 +224,6 @@ Import this stylesheet from your root `+layout.svelte`:
226
224
 
227
225
  ```css title="src/app.css"
228
226
  @import 'tailwindcss';
229
- @import '@c15t/svelte/styles.css';
230
227
 
231
228
  @custom-variant dark (&:where(.dark, .dark *));
232
229
  ```
@@ -256,8 +253,12 @@ c15t's parts, pass the URL of your built stylesheet in
256
253
 
257
254
  ## Set up Tailwind CSS 3
258
255
 
259
- Tailwind 3 loads the same `styles.css` as Tailwind 4 and needs one PostCSS
260
- plugin. Every c15t package that ships a stylesheet exports it as
256
+ Tailwind 3's preflight is unlayered, so it beats the layered rules c15t
257
+ renders itself and strips the banner's padding and borders. On Tailwind 3,
258
+ turn those rules off with `styles: false` and import c15t's `styles.css`
259
+ through a PostCSS plugin. The Astro integration needs only the plugin: it
260
+ detects Tailwind 3 and links `c15t/astro/styles.css` itself. Every c15t
261
+ package that ships a stylesheet exports the plugin as
261
262
  `<package>/postcss-tailwind3`, so use the one from the package you installed:
262
263
 
263
264
  | You installed | Plugin name |
@@ -269,21 +270,26 @@ plugin. Every c15t package that ships a stylesheet exports it as
269
270
  They are the same plugin. A config that already lists
270
271
  `@c15t/ui/postcss-tailwind3` keeps working.
271
272
 
272
- Make three changes:
273
+ Make four changes:
273
274
 
274
- 1. Add the plugin to your PostCSS plugins, before `tailwindcss`. Write the plugins as an object, as the examples below do.
275
+ 1. Set `styles: false` where you configure c15t: `options` on `ConsentRoot`
276
+ in Next.js and TanStack Start, `options` on `ConsentProvider` in React,
277
+ and `styles={false}` on `ConsentManagerProvider` in Svelte and SvelteKit.
278
+ Vue, Nuxt and Astro need no change.
279
+ 2. Add the plugin to your PostCSS plugins, before `tailwindcss`. Write the plugins as an object, as the examples below do.
275
280
  PostCSS runs them in the order of the keys. Next.js also accepts an array
276
281
  of plugin names, but Vite's PostCSS loader rejects that form.
277
- 2. Where you import c15t's stylesheet yourself, put the import above the
278
- `@tailwind` directives, whatever your build tool. Vite drops an `@import`
282
+ 3. Import c15t's stylesheet above the `@tailwind` directives, whatever your
283
+ build tool. Vue, Nuxt and Astro skip this step. Vite drops an `@import`
279
284
  that follows another rule, with the warning
280
285
  `@import must precede all other statements`, and the banner renders
281
286
  unstyled. Next.js 16 with Turbopack fails the build.
282
- 3. Set `darkMode: 'class'` so Tailwind's `dark:` variant follows the same
287
+ 4. Set `darkMode: 'class'` so Tailwind's `dark:` variant follows the same
283
288
  `dark` class c15t reads.
284
289
 
285
- The plugin also processes the stylesheets c15t loads for you, such as Astro's
286
- injected one. Without it, Tailwind 3 stops
290
+ The plugin also processes the stylesheets the Vue and Nuxt components import,
291
+ and the one the Astro integration adds on a Tailwind 3 site. Without it,
292
+ Tailwind 3 stops
287
293
  the build with "`@layer components` is used but no matching
288
294
  `@tailwind components` directive is present", or purges c15t's rules, because
289
295
  c15t's class names never appear in your source.
@@ -293,8 +299,9 @@ class. Add the important modifier to utilities you pass to c15t parts:
293
299
  `!bg-red-500`, `hover:!bg-red-500`. A bare `bg-red-500` has no effect. Theme
294
300
  tokens work the same on Tailwind 3 and 4.
295
301
 
296
- In React and Next.js apps, `c15t setup` makes the first two changes when it
297
- finds Tailwind 3. It replaces an existing
302
+ In React and Next.js apps, `c15t setup` adds the plugin and the stylesheet
303
+ import when it finds Tailwind 3, but does not set `styles: false`. Add that
304
+ yourself. It replaces an existing
298
305
  `styles.tw3.css` import from an earlier setup with `styles.css`. When it
299
306
  cannot edit your PostCSS config, it prints the step to do by hand. See
300
307
  [setup](https://c15t.com/docs/cli/commands/setup#set-up-styles-and-tailwind-css).
@@ -327,7 +334,7 @@ export default {
327
334
 
328
335
  Import `c15t/next/styles.css` above the directives. Turbopack fails the
329
336
  build when the import sits below them. The same order works with
330
- webpack:
337
+ webpack. Set `styles: false` in `ConsentRoot`'s `options` as well:
331
338
 
332
339
  ```css title="app/globals.css"
333
340
  /* Turbopack rejects an @import below other rules, so it goes first. */
@@ -358,7 +365,8 @@ export default {
358
365
  ```
359
366
 
360
367
  Import `c15t/tanstack-start/styles.css` above the directives, in the
361
- stylesheet your root route links:
368
+ stylesheet your root route links, and set `styles: false` in
369
+ `ConsentRoot`'s `options`:
362
370
 
363
371
  ```css title="src/styles.css"
364
372
  /* Vite ignores an @import below other rules, so it goes first. */
@@ -388,8 +396,9 @@ export default {
388
396
  } satisfies Config;
389
397
  ```
390
398
 
391
- Import `c15t/react/styles.css` above the directives, and import this
392
- stylesheet from your entry module:
399
+ Import `c15t/react/styles.css` above the directives, import this
400
+ stylesheet from your entry module, and set `styles: false` in the
401
+ `ConsentProvider` options:
393
402
 
394
403
  ```css title="src/app.css"
395
404
  /* Vite ignores an @import below other rules, so it goes first. */
@@ -489,8 +498,12 @@ export default {
489
498
  } satisfies Config;
490
499
  ```
491
500
 
492
- The plugin processes the stylesheet the integration injects, so the CSS
493
- entry holds only the directives:
501
+ When the integration finds Tailwind 3 in the project, it adds
502
+ `c15t/astro/styles.css` to every page through Astro's CSS pipeline
503
+ instead of inlining the banner's rules, so the plugin processes it. You
504
+ do not set `styles: false` or import c15t's stylesheet, and the CSS
505
+ entry holds only the directives. The first paint still waits for that
506
+ stylesheet:
494
507
 
495
508
  ```css title="src/styles/global.css"
496
509
  @tailwind base;
@@ -521,8 +534,9 @@ export default {
521
534
  } satisfies Config;
522
535
  ```
523
536
 
524
- Import `@c15t/svelte/styles.css` above the directives, and import this
525
- stylesheet from `src/main.ts`:
537
+ Import `@c15t/svelte/styles.css` above the directives, import this
538
+ stylesheet from `src/main.ts`, and pass `styles={false}` to
539
+ `ConsentManagerProvider`:
526
540
 
527
541
  ```css title="src/app.css"
528
542
  /* Vite ignores an @import below other rules, so it goes first. */
@@ -556,8 +570,9 @@ export default {
556
570
  } satisfies Config;
557
571
  ```
558
572
 
559
- Import `@c15t/svelte/styles.css` above the directives, and import this
560
- stylesheet from your root `+layout.svelte`:
573
+ Import `@c15t/svelte/styles.css` above the directives, import this
574
+ stylesheet from your root `+layout.svelte`, and pass `styles={false}` to
575
+ `ConsentManagerProvider`:
561
576
 
562
577
  ```css title="src/app.css"
563
578
  /* Vite ignores an @import below other rules, so it goes first. */
@@ -8,14 +8,14 @@ group: customization
8
8
  ## Where tokens come from
9
9
 
10
10
  c15t's components take their colors from `--c15t-*` CSS variables, and most
11
- of their fonts, radii, spacing, shadows and motion too. c15t's stylesheet sets
12
- the defaults. You change them with a theme object or with CSS, and every part
11
+ of their fonts, radii, spacing, shadows and motion too. c15t's rules set the
12
+ defaults. You change them with a theme object or with CSS, and every part
13
13
  that reads a token changes with it. Some values are still fixed for one
14
14
  component, such as button padding, the radius of the "Secured by" tag and
15
15
  several font sizes and weights, so a token change does not move them. Restyle
16
16
  those parts through [slots](./slots.md) or your own CSS.
17
17
  [Stylesheets and CSS layers](./stylesheets.md)
18
- covers which stylesheet to load, and [dark mode](./dark-mode.md)
18
+ covers how c15t loads its rules, and [dark mode](./dark-mode.md)
19
19
  covers the dark set of tokens.
20
20
 
21
21
  ## Set semantic values together
@@ -59,9 +59,9 @@ setup.
59
59
  ## Combine a generated theme with your own CSS
60
60
 
61
61
  `generateThemeCSS` writes its variables on `:root:root` and
62
- `.c15t-theme-root.c15t-theme-root`, one step more specific than the defaults
63
- in `styles.css`. The theme therefore overrides the defaults whether its
64
- `<style>` element comes before or after the stylesheet. The same output backs
62
+ `.c15t-theme-root.c15t-theme-root`, more specific than c15t's defaults. The
63
+ theme therefore overrides the defaults whether its
64
+ `<style>` element comes before or after c15t's rules. The same output backs
65
65
  `ConsentTheme` in React, Next.js and TanStack Start, Astro's `theme` option and
66
66
  the script tag's `ui.theme`.
67
67