@c15t/react 3.0.0-alpha.7 → 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.
- package/AGENTS.md +3 -3
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/iab-panel/atoms/root.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/prompt.js +1 -1
- package/dist/components/shared/dialog-sheets.js +1 -0
- package/dist/components/shared/first-paint-sheets.js +1 -0
- package/dist/components/shared/iab-dialog-sheets.js +1 -0
- package/dist/components/shared/iab-first-paint-sheets.js +1 -0
- package/dist/components/shared/primitives/legal-links/index.js +1 -1
- package/dist/components/shared/streamed-surface.js +2 -0
- package/dist/components/shared/surface-styles.js +2 -0
- package/dist/context.js +1 -1
- package/dist/hooks/use-iab-policy-guard.js +1 -0
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +1 -1
- package/dist/iab-context.js +1 -1
- package/dist/kernel-selector.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/components/prompt/atoms/root.d.ts +4 -0
- package/dist-types/components/prompt/prompt.d.ts +4 -0
- package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
- package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
- package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
- package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
- package/dist-types/components/shared/streamed-surface.d.ts +23 -0
- package/dist-types/components/shared/surface-styles.d.ts +36 -0
- package/dist-types/context.d.ts +17 -1
- package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
- package/dist-types/kernel-selector.d.ts +10 -0
- package/dist-types/provider.d.ts +36 -4
- package/dist-types/ui-config-context.d.ts +4 -0
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +3 -3
- package/docs/concepts/choose-your-setup.md +4 -2
- package/docs/concepts/data-fetching.md +3 -1
- package/docs/customization/overview.md +3 -2
- package/docs/customization/stylesheets.md +163 -50
- package/docs/customization/tailwind.md +59 -44
- package/docs/customization/tokens.md +6 -6
- package/docs/frameworks/react/components/consent-banner.md +7 -4
- package/docs/frameworks/react/components/consent-dialog.md +8 -5
- package/docs/frameworks/react/components/consent-provider.md +41 -36
- package/docs/frameworks/react/components.md +9 -5
- package/docs/frameworks/react/compose.md +3 -3
- package/docs/frameworks/react/content-security-policy.md +22 -8
- package/docs/frameworks/react/customize.md +21 -13
- package/docs/frameworks/react/iab.md +36 -4
- package/docs/frameworks/react/quickstart.md +84 -85
- package/docs/frameworks/react/rendering.md +24 -12
- package/docs/frameworks/react/scripts.md +7 -4
- package/docs/frameworks/react/troubleshooting.md +70 -19
- package/docs/frameworks/react/upgrade-v3.md +23 -17
- package/docs/frameworks/react/vendor-consent.md +19 -7
- package/docs/guides/banner-experiments.md +2 -4
- package/docs/guides/troubleshooting.md +9 -9
- package/package.json +7 -7
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* React 18's way to suspend: throw the promise, and read its recorded
|
|
5
|
+
* result on the retry. The runtime's `settled` records its own result.
|
|
6
|
+
* Exported for tests.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const readRecorded: <Value>(thenable: PromiseLike<Value>) => Value;
|
|
11
|
+
/**
|
|
12
|
+
* Renders a consent surface the server can stream. While the provider's
|
|
13
|
+
* `prefetch` is a promise, the surface waits for it in its own Suspense
|
|
14
|
+
* boundary: the rest of the page streams at once, and the surface follows
|
|
15
|
+
* in a later chunk of the same response, rendered from the resolved
|
|
16
|
+
* config. It shows only when that config carries a policy that shows it.
|
|
17
|
+
* Hydration reads the same config, so the surface hydrates in place.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare const StreamedSurface: ({ children }: {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}) => string | number | bigint | boolean | React.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ReactPortal | null | undefined> | null | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** A stylesheet from `@c15t/ui/styles/sheets/*`. */
|
|
2
|
+
export interface SurfaceStyleSheet {
|
|
3
|
+
/** Dedupes the stylesheet: a page renders each `id` once. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** The stylesheet text. */
|
|
6
|
+
css: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Renders the stylesheets a stock surface needs as `<style>` elements.
|
|
10
|
+
*
|
|
11
|
+
* The banner, trigger, ConsentGate, dialog and widget each render the
|
|
12
|
+
* sheets they use, so an app that imports no c15t stylesheet still gets
|
|
13
|
+
* styled surfaces, and no stylesheet `<link>` holds back the first paint.
|
|
14
|
+
* A server-rendered surface puts its rules in the HTML. A surface that
|
|
15
|
+
* renders in the browser, like the dialog, inserts them with its own code.
|
|
16
|
+
*
|
|
17
|
+
* In React 19 the elements move to `<head>` and each `id` renders once per
|
|
18
|
+
* page; later sheets follow earlier ones, which keeps the cascade order of
|
|
19
|
+
* `styles.css`. React 18 renders them in place, next to the surface, and
|
|
20
|
+
* so does React 19 when the provider sets a `nonce`: React drops the nonce
|
|
21
|
+
* from a style it moves to `<head>` unless the app passed the same nonce to
|
|
22
|
+
* React's server renderer, and a style without it would break a
|
|
23
|
+
* nonce-based policy.
|
|
24
|
+
*
|
|
25
|
+
* Renders nothing when the provider sets `styles: false` (the app imports
|
|
26
|
+
* `styles.css` itself) or `noStyle`.
|
|
27
|
+
*
|
|
28
|
+
* @param props.sheets - The stylesheets, in cascade order.
|
|
29
|
+
* @param props.noStyle - The surface's own `noStyle` prop.
|
|
30
|
+
* @returns The `<style>` elements, or `null`.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare const SurfaceStyles: ({ sheets, noStyle, }: {
|
|
34
|
+
sheets: readonly SurfaceStyleSheet[];
|
|
35
|
+
noStyle?: boolean;
|
|
36
|
+
}) => import("react").JSX.Element[] | null;
|
package/dist-types/context.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AllConsentNames, ConsentKernel, ConsentPresentation } from '@c15t/core';
|
|
1
|
+
import type { AllConsentNames, ConsentKernel, ConsentPresentation, ConsentSnapshot } from '@c15t/core';
|
|
2
|
+
import type { StreamedPrefetch } from '@c15t/core/runtime';
|
|
2
3
|
/**
|
|
3
4
|
* React context carrying the kernel instance.
|
|
4
5
|
*
|
|
@@ -21,3 +22,18 @@ export interface ProviderServices {
|
|
|
21
22
|
setLanguage: (code: string) => void;
|
|
22
23
|
}
|
|
23
24
|
export declare const ProviderServicesContext: import("react").Context<ProviderServices | null>;
|
|
25
|
+
/**
|
|
26
|
+
* The provider runtime's streamed `prefetch`, while `prefetch` is a promise.
|
|
27
|
+
* Consent surfaces render inside a boundary that waits for it.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare const StreamedPrefetchContext: import("react").Context<StreamedPrefetch | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* The snapshot a streamed consent surface was server-rendered from, which
|
|
34
|
+
* its hydration must read in place of the kernel's own server snapshot.
|
|
35
|
+
* `null` everywhere else.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare const HydrationSnapshotContext: import("react").Context<ConsentSnapshot | null>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IABUnavailableError } from '@c15t/core';
|
|
2
|
+
/**
|
|
3
|
+
* Record that `IABProvider` is part of the app. `@c15t/react/iab` calls
|
|
4
|
+
* this when it loads.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare const markIABProviderLoaded: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* The error the standard surfaces throw for an `iab` policy without
|
|
11
|
+
* `<IABProvider>`.
|
|
12
|
+
*
|
|
13
|
+
* @returns A fresh {@link IABUnavailableError}.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare const missingIABProviderError: () => IABUnavailableError;
|
|
17
|
+
/**
|
|
18
|
+
* Throw while rendering when the visitor's policy needs a CMP and the app
|
|
19
|
+
* has none.
|
|
20
|
+
*
|
|
21
|
+
* The standard banner and dialog do not answer for the IAB model, so
|
|
22
|
+
* without `<IABProvider>` a visitor under an `iab` policy would see no
|
|
23
|
+
* consent UI at all. Throwing during render reaches error boundaries and
|
|
24
|
+
* framework error overlays, on the server render and in the browser alike,
|
|
25
|
+
* since both read the same snapshot.
|
|
26
|
+
*
|
|
27
|
+
* `<IABProvider>` usually sits beside the standard surfaces rather than
|
|
28
|
+
* around them, so a missing IAB context alone does not prove the app has
|
|
29
|
+
* none. The check also passes once `@c15t/react/iab` has loaded.
|
|
30
|
+
*
|
|
31
|
+
* @throws {IABUnavailableError} When the policy needs IAB and neither an
|
|
32
|
+
* IAB context nor `@c15t/react/iab` is present.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare const useIABPolicyGuard: () => void;
|
|
@@ -16,6 +16,16 @@ import type { AllConsentNames, ConsentKernel, ConsentSnapshot } from '@c15t/core
|
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
18
|
export declare const useKernel: () => ConsentKernel;
|
|
19
|
+
/**
|
|
20
|
+
* Reads the snapshot hydration renders: the kernel's server snapshot, or,
|
|
21
|
+
* inside a streamed consent surface, the snapshot the server rendered that
|
|
22
|
+
* surface from.
|
|
23
|
+
*
|
|
24
|
+
* @param kernel - The provider's kernel.
|
|
25
|
+
* @returns A `getServerSnapshot` for `useSyncExternalStore`.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare const useServerSnapshot: (kernel: ConsentKernel | null) => () => ConsentSnapshot | undefined;
|
|
19
29
|
/**
|
|
20
30
|
* Subscribes to one slice of the kernel snapshot. React re-renders the
|
|
21
31
|
* caller only when the selected value `Object.is`-differs, so a selector
|
package/dist-types/provider.d.ts
CHANGED
|
@@ -40,11 +40,29 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
|
|
|
40
40
|
* @remarks
|
|
41
41
|
* Set this when your CSP uses a nonce-based policy instead of
|
|
42
42
|
* `'unsafe-inline'`. The provider forwards it to every `<script>`
|
|
43
|
-
* element created by the script loader
|
|
43
|
+
* element created by the script loader, and to the `<style>` elements
|
|
44
|
+
* the stock surfaces render. A per-script `nonce` still takes
|
|
44
45
|
* precedence. Pass the same nonce to `ConsentTheme`, which renders the
|
|
45
46
|
* theme `<style>` element.
|
|
46
47
|
*/
|
|
47
48
|
nonce?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the stock surfaces render their own stylesheets.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* The banner, dialog trigger, ConsentGate, dialog and preference widget
|
|
54
|
+
* each render the c15t rules they use as a `<style>` element: in the
|
|
55
|
+
* server-rendered HTML, or with the surface's own code in the browser.
|
|
56
|
+
* No c15t stylesheet `<link>` then holds back the page's first paint.
|
|
57
|
+
* React 19 moves them into `<head>` and renders each once. They carry
|
|
58
|
+
* {@link ConsentProviderOptions.nonce}.
|
|
59
|
+
*
|
|
60
|
+
* Set `false` when the app imports `styles.css` itself, for example to
|
|
61
|
+
* run it through Tailwind CSS 3 or to put it in a named cascade layer.
|
|
62
|
+
*
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
styles?: boolean;
|
|
48
66
|
/**
|
|
49
67
|
* Transport factory the provider builds its kernel with. Required.
|
|
50
68
|
*
|
|
@@ -85,9 +103,12 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
|
|
|
85
103
|
* Pass the pending `Promise<KernelConfig>` instead and the provider
|
|
86
104
|
* mounts at once with a provisional policy, so `children` render (and
|
|
87
105
|
* the static shell can prerender) while the consent data streams in.
|
|
88
|
-
* Consent surfaces stay hidden until the promise resolves
|
|
89
|
-
* `
|
|
90
|
-
*
|
|
106
|
+
* Consent surfaces stay hidden until the promise resolves. On the
|
|
107
|
+
* server, `ConsentBanner` waits for it in its own Suspense boundary and
|
|
108
|
+
* follows the page in a later chunk of the same response, before
|
|
109
|
+
* hydration, when the resolved policy shows a banner; see
|
|
110
|
+
* `streamBanner`. The resolved config then answers the first `init()`
|
|
111
|
+
* in place of the transport's network init. A config that resolves without a policy (persisted
|
|
91
112
|
* consents, geo, language only) is applied to the kernel and the
|
|
92
113
|
* transport init runs as usual; a rejected promise is logged outside
|
|
93
114
|
* production and falls through to the transport init. Initial-only:
|
|
@@ -105,6 +126,17 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
|
|
|
105
126
|
* ```
|
|
106
127
|
*/
|
|
107
128
|
prefetch?: ConsentProviderPrefetch | Promise<ConsentProviderPrefetch>;
|
|
129
|
+
/**
|
|
130
|
+
* Whether `ConsentBanner` renders on the server once a `prefetch`
|
|
131
|
+
* promise resolves, so it arrives in the streamed response before the
|
|
132
|
+
* page hydrates. It shows only when the resolved policy shows a banner
|
|
133
|
+
* for this visitor. Set `false` to mount the banner after hydration
|
|
134
|
+
* instead, as with a client-only provider. No effect when `prefetch` is
|
|
135
|
+
* not a promise. Use the same value on the server and in the browser.
|
|
136
|
+
*
|
|
137
|
+
* @default true
|
|
138
|
+
*/
|
|
139
|
+
streamBanner?: boolean;
|
|
108
140
|
callbacks?: ConsentProviderCallbacks;
|
|
109
141
|
/**
|
|
110
142
|
* Remove configured browser data when its consent permission is revoked.
|
|
@@ -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;
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.0.0-alpha.
|
|
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
|
|
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):
|
|
78
|
-
- [Tailwind CSS](./customization/tailwind.md):
|
|
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
|
|
|
@@ -13,7 +13,7 @@ to the alternatives it rules out.
|
|
|
13
13
|
| Your app | Setup | Guide |
|
|
14
14
|
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
15
|
| Next.js App Router with a Next.js server | Server resolves consent from a build-time manifest and streams it to the page | [App Router](https://c15t.com/docs/frameworks/next/quickstart) |
|
|
16
|
-
| Next.js App Router,
|
|
16
|
+
| Next.js App Router, page must arrive with its banner | Await consent inside `Suspense` | [Stream or await](https://c15t.com/docs/frameworks/next/rendering#stream-or-await-consent-in-the-app-router) |
|
|
17
17
|
| Next.js with static, ISR, `'use cache'` or `ensureStatic = 'navigation'` pages | Browser resolves consent on those pages | [Static, ISR and cached pages](https://c15t.com/docs/frameworks/next/rendering#render-static-isr-and-cached-pages) |
|
|
18
18
|
| Next.js Pages Router | Resolve consent in `getServerSideProps` | [Pages Router](https://c15t.com/docs/frameworks/next/rendering#pick-your-rendering-path) |
|
|
19
19
|
| Next.js with `output: 'export'` | Browser resolves consent | [Static export](https://c15t.com/docs/frameworks/next/static-export) |
|
|
@@ -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
|
|
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
|
|
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):
|
|
71
|
-
|
|
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:
|
|
4
|
-
|
|
5
|
-
c15t
|
|
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
|
-
##
|
|
10
|
-
|
|
11
|
-
| Framework
|
|
12
|
-
|
|
|
13
|
-
| Next.js
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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.
|