Skip to main content

SvelteKit Customization

Customize

Pick the right tool

ChangeUse
Brand colors, radius, type, spacinggenerateThemeCSS() in a server load, or --c15t-* variables in a stylesheet
One part of one componenttheme.slots on the provider
Button styles by actiontheme.consentActions
Shape, position, blocking, button orderConsentBanner props or presentation
Wording and languagesConsentBanner text props, or translations
Different markupHeadless

Customization explains how the pieces fit together 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.

Render brand tokens on the server

The provider's theme prop does not turn colors, radius or other tokens into CSS in the browser. Generate the CSS on the server instead, so the first paint already uses your brand. Install @c15t/ui, which has the generator:

npm install @c15t/ui@alpha

Put the theme in a module under src/lib/server. SvelteKit refuses to import server-only modules into browser code, so the generator never reaches the bundle:

src/lib/server/consent-theme.ts
import { generateThemeCSS } from '@c15t/ui/theme';

// Modules in a `server` directory never reach the browser, so the theme
// generator stays on the server. The CSS is generated once, when the server
// starts.
export const themeCSS = generateThemeCSS({
	colors: {
		primary: '#6943a3',
		primaryHover: '#533285',
		textOnPrimary: '#ffffff',
	},
	radius: { lg: '18px' },
});

Return the CSS from a layout load. If your root +layout.server.ts already exports loadConsent from the quickstart, call it from your own load and return themeCSS next to its consent, as in ({ ...(await loadConsent(event)), themeCSS }). A separate layout looks like this:

src/routes/+layout.server.ts
import { themeCSS } from '#lib/server/consent-theme.js';

import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = () => ({ themeCSS });

Render it in <svelte:head> in the layout:

src/routes/+layout.svelte
<svelte:head>
	<!-- generateThemeCSS escapes `<`, so its output is safe in a style element. -->
	{@html `<style id="c15t-theme">${data.themeCSS}</style>`}
</svelte:head>

SvelteKit writes <svelte:head> content before its stylesheet links, and c15tHandle adds c15t's default rules at the end of <head>. The generateThemeCSS selectors are more specific than those defaults, so the theme still wins. Keep the c15t-theme id. In development, the provider logs a warning when its theme prop has tokens and the page has no <style id="c15t-theme">. generateThemeCSS escapes <, so its output is safe inside a <style> element.

generateThemeCSS fills in what you leave out, such as a readable text color for your primary color. Pass a color scheme as its second argument, 'dark' or 'system', to include dark tokens. Keep slot styles and consentActions in the provider's theme prop; the server CSS covers tokens only. Theme tokens lists every token.

Use a stylesheet instead

For fixed brand values you can skip the generator. Write the --c15t-* variables on :root in a stylesheet and import it in the root layout:

src/consent-theme.css
/* Load after @c15t/svelte/styles.css so these values win. */
:root {
	--c15t-primary: #6943a3;
	--c15t-primary-hover: #533285;
	--c15t-text-on-primary: #ffffff;
	--c15t-radius-lg: 18px;
}

Set the hover and text colors yourself; a stylesheet does not derive them.

Pick one of the two for each token. generateThemeCSS output outranks a plain :root rule, so a --c15t-* value you set on :root loses to the generated theme wherever the two overlap. To override a generated value from your stylesheet, write the rule on :root:root, or move the value into the theme.

Switch to dark tokens

The provider's colorScheme prop switches on the dark token set: 'dark' always, 'system' when the operating system prefers dark, 'light' never. It toggles a c15t-dark class on <html>. Left unset, the provider copies a dark class on <html> into c15t-dark, so an app with its own dark mode toggle gets the dark tokens without the prop. Pass null if your app sets c15t-dark itself. Override dark values under :root.dark and :root.c15t-dark in your stylesheet. Dark mode covers a dark first paint.

Style one part with theme slots

The provider's theme prop still applies slot styles in the browser. A slot takes a class name, or { className, style, noStyle }. Svelte applies a slot's class and style on every stock part, including the IAB banner and dialog:

const theme = {
	slots: {
		consentBannerCard: 'brand-banner-card',
		consentBannerTitle: 'brand-banner-title',
	},
};

Pass it as theme={theme} on ConsentProvider. Define the class in a global stylesheet, or with :global(.brand-banner-card) in a component, because Svelte scopes component styles. The Svelte components read these slots:

ComponentSlots
ConsentBannerconsentBanner, consentBannerCard, consentBannerHeader, consentBannerTitle, consentBannerDescription, consentBannerFooter, consentBannerFooterSubGroup, consentBannerRights, consentBannerRightLink, consentBannerTag, consentBannerOverlay
ConsentDialogconsentDialog, consentDialogCard, consentDialogHeader, consentDialogTitle, consentDialogDescription, consentDialogContent, consentDialogTag
ConsentWidgetconsentWidget, consentWidgetFooter, consentWidgetFooterSubGroup, consentWidgetTag
ConsentDialogTriggerconsentDialogTrigger, consentDialogTriggerIcon
IABConsentBanneriabConsentBanner, iabConsentBannerTag, iabConsentBannerOverlay
ConsentGateconsentGate, consentGateTitle, consentGateButton
Buttons in every componentbuttonPrimary, buttonSecondary

Slot names are not React's components.banner.card keys; do not copy a React configuration. Use slots when one part should change and tokens when a value should change everywhere. class on a component, such as <ConsentBanner class="...">, goes on its root. Component parts explains the attributes each element carries, and Tailwind CSS shows the Svelte and SvelteKit setups for Tailwind 4 and 3.

Change button styles by action

theme.consentActions picks each button's style by what it does, whichever order the policy puts the buttons in:

const theme = {
	consentActions: {
		default: { variant: 'neutral', mode: 'stroke' },
		primary: { variant: 'primary', mode: 'filled' },
		reject: { variant: 'neutral', mode: 'filled' },
	},
};

A per-action entry overrides primary, which overrides default. The policy decides which action is primary.

Change the banner shape and position

Set the shape on the banner itself, or for every surface with the provider's presentation prop:

<ConsentBanner variant="bar" position="bottom" />
variantPositions
floatingbottom-left (default), bottom-right, top-left, top-right, bottom-center, top-center
barbottom (default), top
widgetbottom-right (default), bottom-left, top-left, top-right
wallcenter

blocking adds a backdrop, locks scrolling, traps focus and stops outside clicks from closing the banner. layout and primaryButton change the button order and emphasis. The policy wins where they conflict. A policy that requires a reject button keeps it, whatever the layout says.

Change the copy

For one banner, the text props on ConsentBanner, such as title and rejectButtonText, replace the copy in every mode:

<ConsentBanner title="Your privacy" rejectButtonText="Reject optional" />

For site-wide copy, pass the provider's i18n messages. In offline() mode they merge over the bundled English copy. In hosted() mode they merge over the backend's copy for the same language, key by key, so keys you leave out keep the wording from your Inth project. Translations covers both, the message keys and switching languages. Copy and translations covers wording for each action.

Turn off animations

disableAnimation on the provider turns off the banner and dialog animations, and the same prop on ConsentBanner, ConsentDialog, IABConsentBanner or IABConsentDialog overrides it for one surface. Left unset, the provider follows the visitor's reduced motion setting. Motion and animation covers the duration and easing tokens.

Remove c15t's styles

Set noStyle on one component or on the provider to render the same markup and behavior without c15t's classes. Style the data-* attributes and your slot classes instead. For different markup, see the headless page.