---
title: ConsentRoot
description: Mount ConsentRoot in a SvelteKit root layout with the state
  loadConsent returns, so the banner is in the first HTML, with every prop and
  its default.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Mount ConsentRoot in the root layout

Export `loadConsent` as the root layout's server load, then pass its
`consent` to `ConsentRoot` as `state`:

```ts title="src/routes/+layout.server.ts"
export { loadConsent as load } from '@c15t/svelte/kit';
```

```svelte title="src/routes/+layout.svelte"
<script lang="ts">
	import { posthog } from '@c15t/integrations/posthog';
	import {
		ConsentBanner,
		ConsentDialog,
		ConsentDialogLink,
		ConsentRoot,
	} from '@c15t/svelte';

	let { children, data } = $props();

	const scripts = [
		posthog({
			id: 'phc_your_project_key',
			initOptions: { cookieless_mode: 'never' },
			loadMode: 'after-consent',
		}),
	];
</script>

<ConsentRoot state={data.consent} {scripts}>
	{@render children()}
	<footer>
		<ConsentDialogLink>Privacy settings</ConsentDialogLink>
	</footer>
	<ConsentBanner />
	<ConsentDialog />
</ConsentRoot>
```

Pass `data.consent` through unchanged. It holds the visitor's
server-resolved consent, the mode `c15tHandle()` was given, the backend URL
and the consent route's prefix. `ConsentRoot` renders `ConsentProvider` from
it, on the server and in the browser, so both render the same banner and the
browser makes no second policy request. The [quickstart](../quickstart)
explains `c15tHandle` and `loadConsent`.

`ConsentRoot` turns the mode into a transport that loads each policy path
only when it runs. A page the server resolved ships a small transport that
saves choices, and no resolver, policy rules, snapshot or other languages.
The code to resolve the policy again in the browser, such as after a
language change or on a prerendered page, loads when it is needed.

To use a transport of your own, pass `custom()` from `@c15t/svelte` as
`ConsentRoot`'s `mode` prop. `c15tHandle()` takes only data modes.

Keep the provider in the root `+layout.svelte`. A layout stays mounted across
client-side navigation, so the runtime, the loaded scripts and the dialog
survive page changes. A provider in a page component would start again on
every navigation.

Create `scripts` and `callbacks` in the layout component, not in
`+layout.server.ts`. They hold functions, which a server load cannot send to
the browser.

## Props

Pass each option as a top-level prop, or group them in `options`. When both
set the same option, the top-level prop wins. The `ConsentManagerOptions` type
from `@c15t/svelte` lists every option. SvelteKit's `ConsentRoot` takes the
same props, except that `state` replaces `mode` and `prefetch`.

|Prop|Type|Default|Behavior|
|--|--|--|--|
|`mode`|`manifest()`, `hosted()`, `offline()` or `custom()` result, all from `@c15t/svelte`|required|Where policies come from and where choices are saved. Read once; remount the provider to change it. `ConsentRoot` builds it from `state`, or takes a `custom()` transport as `mode`.|
|`prefetch`|`ConsentState`|none|Server-resolved state, such as the result of `resolveConsent`. With a resolved policy in it, the first render already knows whether to show the banner, and the browser skips its own policy request. `ConsentRoot` passes it from `state`. Read once.|
|`scripts`|`Script[]`|none|Vendor scripts that load when their category is allowed. Updates after mount: new scripts go to the loader. See [scripts](../scripts).|
|`consentCategories`|`AllConsentNames[]`|the policy's categories|Categories the preference UI offers, within the policy's scope. Updates after mount.|
|`theme`|`Theme`|none|Slot classes and `consentActions` button styles. Design tokens in it are not applied in the browser. Updates after mount.|
|`presentation`|`ConsentPresentation`|the policy's defaults|Banner and dialog shape, position, button layout and blocking for every component. Updates after mount.|
|`legalLinks`|`LegalLinks`|none|Privacy policy, cookie policy and terms links shown in the banner and dialog. Updates after mount.|
|`i18n`|`Partial<I18nConfig>`|bundled English|Copy overrides per language. They win key by key over the backend's copy for the same language; see [translations](../translations). Read once.|
|`colorScheme`|`'light'`, `'dark'`, `'system'` or `null`|none|Toggles the `c15t-dark` class on `<html>`. `null` or unset leaves `<html>` alone. Updates after mount.|
|`noStyle`|`boolean`|`false`|Renders every component without c15t's classes. A component's own `noStyle` wins.|
|`disableAnimation`|`boolean`|follows `prefers-reduced-motion`|Turns off enter and exit transitions.|
|`scrollLock`, `trapFocus`|`boolean`|from `presentation`|Defaults for the banner and dialog. `blocking` on a component sets both.|
|`preloadDialog`|`'idle'` or `'intent'`|`'idle'`|When `ConsentDialog` starts loading before its first open. See [ConsentDialog](./consent-dialog).|
|`networkBlocker`|options or `false`|off|Holds `fetch` and XHR requests to listed domains until their category is allowed. Updates after mount: new rules and `enabled` apply, and `false` removes the blocker. The blocker loads on demand. See [network blocker](../network-blocker).|
|`iframeBlocker`|`{ disableAutomaticBlocking?: boolean }` or `false`|on|Gates iframes that carry `data-category`. Updates after mount: `false` removes the blocker and a new `disableAutomaticBlocking` rebuilds it. See [embeds](../embeds).|
|`iab`|`ProviderIABOptions` or `false`|off|IAB TCF settings. Read once. See [IAB TCF](../iab).|
|`callbacks`|`ConsentProviderCallbacks`|none|`onChoiceRecorded`, `onPermissionsChanged`, `onError` and `onBeforeConsentRevocationReload`. The latest functions are called. See [callbacks](../callbacks).|
|`reloadOnConsentRevoked`|`boolean`|`true`|Reloads the page after a save withdraws a category or vendor that was granted.|
|`overrides`|`{ country?, region?, language?, gpc? }`|none|Forces the location, language or Global Privacy Control signal the policy resolves for. A change after mount resolves the policy again.|
|`user`|`User`|none|Identity sent with the policy request and every saved choice, so the choice is linked to your user ID. A different user after mount is identified with the backend; the user you mount with is not identified separately.|
|`storageConfig`|`StorageConfig`|cookie and key `c15t`|Names and lifetime of the stored choice. Read once. On SvelteKit, pass the same name as `cookieName` to the server helpers.|
|`persistence`|`boolean` or options|`true`|`false` keeps choices in memory only. Read once.|
|`clearOnRevocation`|`ClearOnRevocationConfig`|off|Deletes first-party cookies and storage entries when their category is withdrawn. Loads on demand. See [clearing data on revocation](../clear-on-revocation). Read once.|
|`vendors`|`Vendor[]`|none|Vendors offered for vendor-level consent outside IAB. See [vendor consent](../vendor-consent). Updates after mount: a new list replaces the declared vendors.|
|`nonce`|`string`|none|Content Security Policy nonce for the `<script>` elements the script loader adds. See [Content Security Policy](../content-security-policy). Read when the loader starts.|
|`enabled`|`boolean`|`true`|`false` grants every category, hides all consent UI and skips `/init`. Updates after mount: turning it off renders a separate permissive state and keeps the visitor's stored choice for when it is turned back on.|
|`runtime`|`ConsentRuntime`|none|A runtime you created with `createConsentRuntime()`. See [share one runtime](#share-one-runtime).|
|`options`|`ConsentManagerOptions`|none|The same options as one object.|
|`children`|`Snippet`|none|Your app. The provider renders no markup of its own.|

"Read once" options are read when the provider is created. Changing them later
has no effect until the provider remounts, and outside production a change to
`mode`, `i18n` or `experiment` logs a warning. Options that update after mount
are compared with their previous values, so a new `options` object that only
changes `theme` sends no request. The provider has no `policyRules`
option. For local policy rules, pass `offline({ policyRules })` as `mode`.

## What the provider does when it mounts

The provider creates the consent runtime while the component initializes, on
the server and in the browser. Creating it has no side effects, so the server
can render the banner from `prefetch`. In the browser, `onMount` starts the
runtime:

1. It reads the stored choice from the cookie and local storage.
2. It starts the script loader, the iframe blocker and, if configured, the
   network blocker and the IAB TCF add-on.
3. It resolves the policy with its `mode`, unless `prefetch` already holds
   one.

When the provider unmounts, it stops all of them. Keep one provider at the root of the app, so it stays mounted across
navigation and every component can reach it.

With `networkBlocker`, matching requests are held from the moment the provider
is created in the browser, before its children run their own code, until the
blocker decides them.

## Share one runtime

Pass `runtime` when two component trees that cannot share Svelte context need
the same consent state. Create it with `createConsentRuntime()` from
`@c15t/svelte`, pass it to each provider, and call `runtime.start()` and
`runtime.dispose()` yourself. A provider never starts or disposes a runtime it
did not create. Options that build the runtime, such as `mode`, `scripts` and
`callbacks`, come from your `createConsentRuntime()` call; display options such
as `theme` and `noStyle` still apply per provider.

## Verify the provider

Open the page in a private window with DevTools open:

1. The banner appears once the policy resolves. `window.c15t` in the console
   shows `pkg: '@c15t/svelte'`.
2. The Network panel shows the request your mode makes: none for
   `manifest()` with a bundled snapshot, one `/init` for `hosted()`, and
   none when the page came with a server-resolved state.
3. A component that calls `getConsentManager()` renders without the
   `c15t: no v3 consent context` error.
