---
title: Customize
description: 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.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Pick the right tool

|Change|Use|
|--|--|
|Brand colors, fonts, radius, spacing|[Theme tokens](#change-colors-fonts-and-radius)|
|Light and dark colors, animation|[Dark mode and motion](#turn-on-dark-mode-and-change-motion)|
|Which button is filled or outlined|[`consentActions`](#style-the-action-buttons)|
|Banner shape and position|[`ConsentBanner` props or `presentation`](#change-the-banner-shape)|
|One element of a component|[Component parts](#style-one-component-part)|
|Labels and languages|[Copy and translations](#change-the-copy)|
|Different markup entirely|[Headless](/docs/frameworks/react/headless)|

[Customization](/docs/customization/overview) explains how these choices relate
across frameworks.

See the [design gallery](/docs/customization/recipes) for five banner designs,
from a bottom bar to a fully custom one, with tested code for this framework.

## Import the stylesheet yourself

A React app imports no c15t stylesheet. `ConsentBanner`, `ConsentDialog` and
the other stock components render the rules they use as `<style>` elements.

Import the stylesheet yourself with Tailwind CSS 3, to put c15t's rules in a
named cascade layer. Set `styles: false` in the provider
options, so the components add no second copy, and import the stylesheet once
in the module that renders `ConsentProvider`:

```tsx title="src/consent.tsx (partial)"
import 'c15t/react/styles.css';

// In the ConsentProvider options:
styles: false,
```

To import the stylesheet from CSS instead, put it at the top of your global
stylesheet, above any `@tailwind` directives. `postcss-import`, which Vite
uses, ignores an `@import` that follows other rules. Tailwind 3 also needs a
PostCSS plugin;
[Tailwind CSS](/docs/customization/tailwind#set-up-tailwind-css-3) shows the
setup.

## Change colors, fonts and radius

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>`.

A single-page app has no server render, so pick one of two ways:

* **Set the variables in your CSS.** Nothing extra ships to the browser:

  ```css title="src/index.css"
  :root {
    --c15t-primary: #2f6f4e;
    --c15t-primary-hover: #24563c;
    --c15t-text-on-primary: #fff;
    --c15t-radius-lg: 4px;
  }
  ```

* **Render `ConsentTheme` next to the provider.** Use this when the theme lives
  in TypeScript or changes at runtime. The browser downloads the theme
  generator.

  ```tsx title="src/consent-theme.ts"
  import { defineTheme } from 'c15t/react';

  export const theme = defineTheme({
    colors: { primary: '#2f6f4e', primaryHover: '#24563c' },
    radius: { lg: '4px' },
  });
  ```

  In `src/consent.tsx`, render `<ConsentTheme theme={theme} />` from
  `c15t/react` just before `<ConsentProvider>`.

In a server-rendered app, such as React Router framework mode, render
`ConsentTheme` in the root component so the `<style>` element is part of the
server HTML. [Theme tokens](/docs/customization/tokens) lists every variable.

`ConsentTheme` outranks a plain `:root` rule, wherever each one loads. If you
use both, a variable set in both places takes the `ConsentTheme` value. Write
your CSS overrides on `:root:root` to beat it, or move the values into the
theme.

## Style the action buttons

`consentActions` decides how each action looks. `default` applies to every
button, `primary` to the action the policy or your props mark primary, and
`accept`, `reject`, `customize` and `dismiss` to one action each. Pass it
through the provider's `theme` option:

```tsx title="src/consent.tsx"
<ConsentProvider
  options={{
    mode,
    scripts,
    theme: {
      consentActions: {
        primary: { variant: 'primary', mode: 'filled' },
        dismiss: { variant: 'neutral', mode: 'stroke' },
      },
    },
  }}
>
```

The provider's `theme` option only reads `consentActions` and slot styles.
Tokens passed there do nothing, and the provider logs a warning in development.
Changing button styles does not change the policy or expire a saved choice.

## Change the banner shape

`ConsentBanner` takes `variant` (`floating`, `bar`, `widget` or `wall`) and
`position`. To use one shape everywhere, set `presentation.prompt` in the
provider options instead. This banner is a full-width bar for notices and a
card for choices:

```tsx title="src/regional-banner.tsx"
import { ConsentBanner, usePolicyRule } from 'c15t/react';

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

Render `RegionalBanner` in place of `ConsentBanner` inside the provider.
[ConsentBanner](/docs/frameworks/react/components/consent-banner#variants)
lists every variant and position, and how to order buttons per policy.

## Style one component part

Each component part is a slot. Set slots in the provider options under
`components`, keyed by component and part. A slot takes any attribute the
element accepts, such as `className` or `style`.

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
and when to drop the built-in styles with `noStyle`. The `ConsentGate`
placeholder's parts are `components['consent-gate'].root`, `.title` and
`.button`.
[Class names and CSS-in-JS](/docs/customization/class-names) covers CSS
Modules, vanilla-extract, StyleX and Emotion, and
[Tailwind CSS](/docs/customization/tailwind) covers utilities on parts.

## Turn on dark mode and change motion

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

`disableAnimation` in the provider options 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.

## Change the copy

Labels, descriptions and languages come from the translations in your Inth
project and from the provider's `i18n` option. For the visitor's language,
`i18n.messages` replace the project's copy key by key, and keys you leave out
keep the project's wording.
[Copy and translations](/docs/customization/translations) shows the message
keys. For one banner, `ConsentBanner` also accepts text props such as
`dismissButtonText`.

## Check the result

Clear site data and reload under a policy that shows a banner. Check the banner
and the preferences dialog, since both read the same tokens. Test a narrow
window, keyboard focus and contrast on the primary button. In development, the
console warns if `theme` holds tokens but the page has no `c15t-theme` style.
