---
title: Customize
description: Load the c15t stylesheet yourself in Next.js when you need to, and
  change colors, radius, button roles, component parts, dark mode and banner
  shape with ConsentTheme and ConsentRoot options.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## See the designs

See the [design gallery](/docs/customization/recipes) for five banner designs,
from a bottom bar to a fully custom one. Its React code works in Next.js with
the same components imported from `c15t/next`.

## Load the stylesheet yourself

A Next.js app imports no c15t stylesheet. `ConsentBanner`, `ConsentDialog`
and the other stock components render the rules they use as `<style>`
elements, in the server HTML for a server-rendered banner.

Import the stylesheet yourself with Tailwind CSS 3, to put c15t's rules in a
named cascade layer. Set `styles: false` under `options` in
`c15t.config.ts`, so the components add no second copy:

```ts title="c15t.config.ts"
import { defineConsentConfig } from 'c15t/next';

export default defineConsentConfig({ options: { styles: false } });
```

Then import c15t once from the global stylesheet that `app/layout.tsx` or
`pages/_app.tsx` loads:

```css title="styles/globals.css"
@import 'c15t/next/styles.css';
```

c15t's rules live in the cascade layer `components`, and each `<style>`
element the components render opens with Tailwind CSS 4's layer order,
`@layer properties, theme, base, components, utilities;`. Unlayered CSS,
Tailwind 4 utilities and StyleX's atomic classes override c15t's rules
without specificity tricks, whichever stylesheet loads first.
[Stylesheets and CSS layers](/docs/customization/stylesheets) explains the
order and which file holds the dialog's rules.

Tailwind CSS 3 has no cascade layers, and its unlayered preflight beats
c15t's layered rules. Set `styles: false` in the provider options, import
`styles.css` above the `@tailwind` directives, and add the
`c15t/postcss-tailwind3` PostCSS plugin before `tailwindcss`. Utilities
you pass to c15t parts then need the important modifier, such as
`!bg-red-500`. [Tailwind CSS](/docs/customization/tailwind#set-up-tailwind-css-3)
shows the tested setup for each framework.

## Change colors and radius with tokens

Colors, radii, shadows, typography, and motion come from `--c15t-*` custom
properties. The rules the components render set the default values, as
does `styles.css` when you import it with `styles: false`. To change them,
render `ConsentTheme` with your theme where your app renders on the server,
or override the variables directly in your CSS.

`ConsentTheme` renders a `<style id="c15t-theme">` element with the
variables for `theme`. It is not a client component: rendered from a Server
Component or another server-only file, the theme generator stays out of the
browser bundle. Rendered from a client component it still works, but the
browser downloads the generator (about 1.7 KB gzip). The provider no longer
turns `theme` tokens into CSS. It still reads `consentActions` and slot
styles from its `theme` option, and warns in development when `theme` holds
tokens but the page has no `c15t-theme` stylesheet.

|`ConsentTheme` prop|Purpose|
|--|--|
|`theme`|Colors, `dark` colors, typography, spacing, radius, shadows and motion. Defaults to the built-in theme.|
|`colorScheme`|`'dark'` makes the dark tokens the default before hydration. `'system'` follows `prefers-color-scheme` with a CSS media query. Omit it to follow a `dark` or `c15t-dark` class on `<html>`.|
|`nonce`|Content Security Policy nonce for the `<style>` element.|

Pass the same `colorScheme` to `ConsentTheme` and to the provider. If your
app manages dark mode through a root class, such as next-themes' `dark`
class, omit `colorScheme` and include that class in the server HTML.
[Dark mode](/docs/customization/dark-mode) covers `theme.dark`, `null` and
a dark first paint.

To switch between different token sets at runtime, render `ConsentTheme`
from a client component and change its props, which ships the generator.

Outside a component, call `generateThemeCSS(theme, colorScheme)` from
`c15t/react/utils`, or `@c15t/react/utils` with the scoped package. It
writes the same CSS as `ConsentTheme`. Outside React, import it from
`@c15t/ui/theme`. Call it on the server or at build time and put the result in a
`<style>` element or your stylesheet. It escapes `<`, so the result is safe
inside `<style>`.

Define the theme in a module without `'use client'`, so Server and Client
Components can both import it:

```ts title="lib/theme.ts"
import { defineTheme } from 'c15t/next';

export const brandTheme = defineTheme({
	colors: { primary: '#315c47', primaryHover: '#24473a' },
	radius: { lg: '16px' },
});
```

Render `ConsentTheme` from a Server Component layout. This layout themes the
routes under `app/branded`:

```tsx title="app/branded/layout.tsx"
import { ConsentTheme } from 'c15t/next';
import type { ReactNode } from 'react';

import { brandTheme } from '@/lib/theme';

const BrandedLayout = ({ children }: { children: ReactNode }) => (
	<>
		<ConsentTheme theme={brandTheme} />
		{children}
	</>
);

export default BrandedLayout;
```

To theme the whole site, render `<ConsentTheme theme={brandTheme} />` in your
root layout, before `ConsentRoot`. View the page source to check that
the `<style id="c15t-theme">` element is in the server HTML. In the Pages
Router, render `ConsentTheme` in `pages/_document.tsx`, which runs only on the
server.

Earlier v3 alphas generated theme CSS in the browser from the provider's
`theme` option. To migrate:

1. Move the theme into a module without `'use client'`, so server and
   client files can both import it.
2. Render `<ConsentTheme theme={theme} />` next to the consent root, from a
   Server Component or another server-rendered file. Pass `colorScheme` and
   `nonce` if you set them on the provider.
3. Keep passing `theme` to the provider only for `consentActions` and slot
   styles. A theme with tokens only can be removed from the provider.
4. The default tokens the provider used to inject now come with the rules
   the components render. If you import `styles.css` with `styles: false`,
   keep that import.

## Style the action buttons

`consentActions` decides how each action role looks: `default` applies to
every button, `primary` to whichever actions the policy or your props mark
primary, and `accept`, `reject`, `customize`, and `dismiss` to one role each.
Per-action keys win over `primary`, which wins over `default`.

Banner sizing has its own variables. Override them in CSS when a variant
needs a different footprint:

|Variable|Default|Applies to|
|--|--|--|
|`--consent-banner-max-width`|`440px`|`floating` cards|
|`--consent-banner-widget-max-width`|`20rem`|`widget` chips|
|`--consent-banner-wall-max-width`|`30rem`|`wall` cards|

The footer lays out its actions by the card's width, not the viewport's.
With the default `compact` profile, a card narrower than 22rem puts Reject
and Accept on one row and Customize on a full-width row below them.

## Colors by consent model

The primary action can change while the brand color stays the same. Set
`consentActions.primary` once, then select `primaryButton` per policy as
shown in [ConsentBanner](../components/consent-banner#per-policy-buttons).

To give opt-in and opt-out banners different colors, scope the tokens to
the root's attributes. Include hover and foreground colors when overriding
CSS tokens directly:

```css
[data-prompt][data-model='opt-in'] {
	--c15t-primary: #2f6f4e;
	--c15t-primary-hover: #24563c;
	--c15t-text-on-primary: #fff;
}

[data-prompt][data-model='opt-out'] {
	--c15t-primary: #6b3fa0;
	--c15t-primary-hover: #55327f;
	--c15t-text-on-primary: #fff;
}
```

These colors apply to the action marked primary. Button layout and color
changes do not change the policy or expire a saved choice.

## Slots

Each component part is a slot. A slot accepts any attributes the element
takes, so the contract is the same whether you write class strings or pass an
object with `className` and `style`. Set slots on the provider under
`components`, keyed by component and part.

`ConsentTheme` renders tokens only. Components read `consentActions` in the
browser, so pass a theme that contains them through `options` in
`c15t.config.ts`:

```ts title="lib/theme.ts (partial)"
export const actionTheme = defineTheme({
	consentActions: {
		primary: { variant: 'primary', mode: 'filled' },
		dismiss: { variant: 'neutral', mode: 'stroke' },
	},
});
```

```ts title="c15t.config.ts"
import { defineConsentConfig } from 'c15t/next';

import { actionTheme } from './lib/theme';

export default defineConsentConfig({ options: { theme: actionTheme } });
```

Keep your `scripts` and other options in the same call.

## Change the banner's shape

For a full-width notice, use the `bar` variant. In a Client Component inside
the existing root, replace the stock banner with:

```tsx title="components/regional-banner.tsx"
'use client';

import { ConsentBanner, usePolicyRule } from 'c15t/next';

export function RegionalBanner() {
	const policy = usePolicyRule();
	return (
		<ConsentBanner variant={policy?.prompt === 'notice' ? 'bar' : 'floating'} />
	);
}
```

[ConsentBanner](/docs/frameworks/next/components/consent-banner#variants)
lists every variant and position.

To remove the notice card's radius, add these values to the existing
`options.components.banner` slots. The root owns `data-prompt`, so the card
uses Tailwind's `group` selector to read it:

```ts
const bannerSlots = {
	root: { className: 'group' },
	card: { className: 'group-data-[prompt=notice]:rounded-none' },
	rightLink: {
		className: 'underline-offset-4 data-[right=opt-out]:text-red-700',
	},
};

// In the existing options.components object:
// banner: bannerSlots
```

The root also carries `data-variant`, so you can restyle one shape without
touching the others. Tailwind, giving a `bar` a brand-colored top edge and
tighter text:

```tsx
<ConsentProvider
	options={{
		mode,
		presentation: { prompt: { variant: 'bar' } },
		components: {
			banner: {
				root: { className: 'group' },
				card: {
					className:
						'group-data-[variant=bar]:border-t-4 group-data-[variant=bar]:border-t-emerald-600',
				},
			},
			description: {
				banner: { className: 'group-data-[variant=bar]:text-xs' },
			},
		},
	}}
/>
```

`data-variant` sits on the root, so child slots use Tailwind's `group`
prefix to read it.

[Class names and CSS-in-JS](/docs/customization/class-names) shows CSS
Modules, vanilla-extract, StyleX and Emotion classes on a part.

Set `noStyle` on a component to drop the built-in classes from every part and
keep only what your slots pass. Set `noStyle` in the provider options to do it
for every component.

[Component parts](/docs/customization/slots) lists every part key under
`components` and the `data-*` attributes each part carries. Action buttons
carry `data-action`, so a single rule such as `[data-action='dismiss']`
styles one role across every surface. Right links are underlined text by
default, so the only button on a notice is the primary action.

[Component parts](/docs/customization/slots) lists every component's part keys,
including the `ConsentGate` placeholder's `components['consent-gate'].root`,
`.title` and `.button`, and [class names and CSS-in-JS](/docs/customization/class-names) covers CSS
Modules, vanilla-extract, StyleX and Emotion.

## Turn on dark mode and change motion

Pass `colorScheme` to `ConsentTheme` in your layout and the same value in
`options` on `ConsentRoot`. Leave it unset to follow a `dark` class on
`<html>`, such as the one next-themes sets, and add dark colors with
`theme.dark`. [Dark mode](/docs/customization/dark-mode) covers each value
and a dark first paint.

`options.disableAnimation` on `ConsentRoot` turns off the banner and dialog
animations, and the same prop on `ConsentBanner` or `ConsentDialog` overrides
it for one surface. [Motion and animation](/docs/customization/motion) covers
the duration and easing tokens and reduced motion.

## Check the result

Open the page in a fresh session. The banner uses your colors and radius, and
the primary action has the style you set. Choices, policy and actions are the
same as with the default design; customizing never records a choice. The
[customization overview](/docs/customization/overview) covers tokens, parts,
copy and headless UI across frameworks.
