Skip to main content

Astro Customization

Customize

Choose what to change

Astro takes every customization through the c15t() options in astro.config.mjs, and renders it on the server. The options must survive JSON serialization, so they hold values, not functions.

ChangeUse
Brand colors, radius, type, spacingtheme tokens
Which button looks primarytheme.consentActions
Wording on every pagei18n.messages
Wording on one bannerConsentBanner props
Light or dark surfacescolorScheme and theme.dark
One part of a componenttheme.slots, or class on ConsentBanner
AnimationdisableAnimation
Banner position and blockingpresentation
Links to your policieslegalLinks, and the banner's legalLinks prop
Your own CSS from scratchnoStyle on the banner, or styles: false

Customization explains how these choices relate across frameworks.

See the design gallery for five banner designs, from a bottom bar to a fully custom one, with tested code for this framework.

Set theme tokens

Put tokens in the integration's theme option:

astro.config.mjs (partial)
c15t({
	theme: {
		colors: {
			primary: '#146b56',
			primaryHover: '#105442',
			textOnPrimary: '#ffffff',
		},
		radius: { lg: '1.25rem' },
	},
});

ConsentScript renders the tokens on the server as a <style id="c15t-theme"> element, so the banner and the dialog share them from the first paint. Set hover and text colors together with a brand color, so each button state stays readable. Theme tokens lists the tokens.

Put tokens in the integration options, not in the client entrypoint. The browser does not turn tokens into CSS, so tokens in the client entrypoint's theme have no effect.

Style the banner buttons

theme.consentActions sets each button's mode and variant. c15t reads the button's own key first, then primary for the action the policy marks as primary, then default:

astro.config.mjs (partial)
c15t({
	theme: {
		consentActions: {
			primary: { mode: 'filled' },
			customize: { mode: 'ghost', variant: 'neutral' },
		},
	},
});

The IAB TCF banner keeps its own button styles.

Change the copy

For wording on every page and in the dialog, set i18n.messages, keyed by language:

astro.config.mjs (partial)
c15t({
	i18n: {
		messages: {
			en: {
				cookieBanner: {
					title: 'Cookies on this site',
					description:
						'We use cookies to measure traffic and show relevant ads.',
				},
			},
		},
	},
});

The server picks the visitor's language from Accept-Language. Set i18n.locale to force one, or detectLanguage: false to use the default.

i18n.messages merges key by key with the copy for that language, so pass only the keys you change. Setting common.acceptAll keeps every other label in common. In hosted() and manifest() modes, the copy your backend sends is the base, and your i18n.messages override it for the same language. Copy and translations covers the message groups.

For one banner, pass the title, description, acceptButtonText, rejectButtonText, customizeButtonText or dismissButtonText props instead. Translations covers languages and the message keys the banner reads.

Change the banner's shape and position

presentation.prompt sets the banner's shape and where it sits:

astro.config.mjs (partial)
c15t({ presentation: { prompt: { variant: 'bar', position: 'bottom' } } });

variant is 'floating' (default), 'bar', 'widget' or 'wall'. The policy still decides which actions the banner must offer, and a notice never becomes a blocking wall. Keep behavior and appearance separate explains what presentation can and cannot change.

Set light or dark mode

The banner and dialog turn dark when <html> has the c15t-dark class. The integration's colorScheme option decides who sets it:

ValueBehavior
'system' (default)Follows prefers-color-scheme, including changes while the page is open
'dark'Always adds the class
'light'Always removes the class
'none' or nullNever adds or removes the class. Your site sets it

With the first three values, ConsentScript sets the class before first paint, and c15t sets it again after each ClientRouter navigation. Set dark colors in theme.dark. Dark mode explains why Astro follows the system setting by default.

Use 'none' when your site has its own theme switch. Toggle c15t-dark on <html> together with your own dark class. If your theme script does not run on astro:after-swap, set the class again there, because a ClientRouter swap replaces the attributes of <html>.

Define the links once in the integration options:

astro.config.mjs (partial)
c15t({
	legalLinks: {
		privacyPolicy: { href: '/privacy', label: 'Privacy policy' },
		cookiePolicy: { href: '/cookies', label: 'Cookie policy' },
	},
});

ConsentBanner and ConsentDialog show only the ones you list in their legalLinks prop, such as legalLinks={['privacyPolicy', 'cookiePolicy']}. A link without a label reads as the translated name for its type, such as "Privacy Policy". See ConsentBanner legal links.

Style one part of a component

theme.slots in the integration options adds classes or inline styles to a named part of the banner, the IAB banner and the dialogs:

astro.config.mjs (partial)
c15t({
	theme: {
		slots: { consentBannerCard: 'brand-card', buttonPrimary: 'brand-button' },
	},
});

Define the classes in a global stylesheet. class on ConsentBanner goes on the banner root. Component parts lists the slot keys, and Tailwind CSS shows the Astro setup for Tailwind 4 and 3.

Turn off animations

disableAnimation in the integration options skips the banner's entry animation and the dialogs' enter and exit animations. The same prop on ConsentBanner, ConsentDialog, IABConsentBanner or IABConsentDialog overrides it for one surface. c15t's stylesheet already stops these animations for visitors who ask for reduced motion. Motion and animation covers the duration and easing tokens.

Load the stylesheet yourself

ConsentScript, or the first consent component on a layout without it, inlines base and configured IAB banner rules once per page as <style> elements. No c15t stylesheet holds back the first paint. When a visitor first reaches for a dialog, c15t links its rules and waits for them before mounting the surface. Svelte also loads its primitives stylesheet. The IAB panel's rules load only when the IAB dialog opens.

On a Tailwind CSS 3 site, the integration adds c15t/astro/styles.css and, with IAB configured, c15t/astro/iab/styles.css to every page instead, so your c15t/postcss-tailwind3 build processes them. Tailwind 3 sites need no change, but their first paint still waits for that stylesheet. Do not import c15t/astro/styles.css as well. To control the cascade layer order yourself, set styles: false and import the stylesheets from your own CSS. Stylesheets and CSS layers lists the files to import.

For markup with no c15t class names at all, pass noStyle to ConsentBanner and style its data-testid and data-* attributes.

Check your changes

  • View the page source. It contains <style id="c15t-theme"> with your tokens.
  • Open the banner and the preference dialog in light and dark mode. Both use your colors, and every button label is readable.
  • Change the browser language and reload. The copy follows it, or stays in i18n.locale when you set one.
  • Navigate with ClientRouter. The colors and dark mode stay the same.