---
title: ConsentGate
description: Keep a YouTube video or map out of SvelteKit server HTML until its
  consent category is allowed with ConsentGate, and replace its placeholder.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Gate an embed behind consent

`ConsentGate` mounts its children only while one category is allowed, and
shows a placeholder otherwise. Wrap any iframe or widget that sets cookies or
contacts a vendor when it loads. Save it as its own component, for example
in `src/lib`, and render it from any route:

```svelte title="src/YouTubeEmbed.svelte"
<script lang="ts">
	import { ConsentDialogLink, ConsentGate } from '@c15t/svelte';
</script>

<!-- The iframe mounts only while measurement is allowed. -->
<ConsentGate category="measurement">
	{#snippet placeholder()}<div class="placeholder">
			<p>Allow measurement to load this YouTube video.</p>
			<ConsentDialogLink>Choose video permissions</ConsentDialogLink>
		</div>{/snippet}
	<iframe
		title="YouTube video"
		src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
		allow="encrypted-media; picture-in-picture"
		allowfullscreen
	></iframe>
</ConsentGate>
```

`ConsentDialog` must be mounted in the root layout so the placeholder's link
has a dialog to open. [Embeds](../embeds) covers iframes you cannot wrap.

## The gate on the server

`ConsentGate` renders an empty wrapper on the server, even when the visitor's
stored choice allows the category. The embed and the placeholder both appear
in the browser one frame after hydration. So an embed never reaches a cached
or prerendered page, and a visitor who has not allowed it never requests it.
Size the wrapper with `class` to avoid a layout shift.

## Props

|Prop|Type|Default|Behavior|
|--|--|--|--|
|`category`|`AllConsentNames`|required|The category whose permission the content needs, such as `'measurement'` or `'marketing'`.|
|`children`|`Snippet`|none|The gated content. It is not in the DOM until the category is allowed.|
|`placeholder`|`Snippet`|built-in placeholder|Shown while the category is denied.|
|`noStyle`|`boolean`|provider's `noStyle`|Drops c15t's classes from the built-in placeholder.|
|`class`|`string`|none|Class on the wrapper `<div>` that stays in the page in every state.|

## Behavior

`ConsentGate` renders a `<div>` wrapper. Inside it, it renders the children
while the visitor's effective permission for `category` is granted, and the
placeholder otherwise. The permission is the same value as
`getConsentManager().has(category)`. It can be granted before the visitor
chooses, under an opt-out policy, and a recorded grant can be overridden by a
privacy signal such as Global Privacy Control.

* While the category is denied, the children are absent from the DOM, so an
  iframe inside sends no request.
* When the visitor allows the category, the children mount. No reload.
* When the visitor withdraws it, the children unmount and the embed stops.
* Until the component has mounted in the browser, the wrapper is empty. The
  server HTML never contains the children or the placeholder, and the browser
  fills the wrapper one frame after mount.

The built-in placeholder shows `Accept {category} consent to view this content.`
and a button labelled `Enable {category} consent`, with the category's
translated title in place of `{category}`. The copy comes from the
`consentGate.title` and `consentGate.actionButton` translations. The button opens the
preference dialog; it does not grant the category, so mount `ConsentDialog`
in the same provider.

`ConsentGate` does not add its category to the preference dialog. Keep the
category in your policy's scope, and in `consentCategories` if you set that
prop.

## Accessibility

The built-in placeholder is plain text and a `<button>`, so it works without
the embed. Give the iframe a descriptive `title`, and give visitors who
decline another way to the content, such as a link to the video's page or a
store address next to the map. A category the policy blocks cannot be allowed
from preferences.

## Style the placeholder

Set `class` on `ConsentGate` to size the wrapper to the embed, for example an
`aspect-ratio` for a video, so the page does not shift when the embed
replaces the placeholder.

To restyle the built-in placeholder, set the `consentGate` slot for the card,
`consentGateTitle` and `consentGateButton` in the provider's `theme.slots`.
`consentGateButton` applies on top of `buttonPrimary`:

```ts
const theme = {
	slots: {
		consentGate: 'brand-gate',
		consentGateButton: { className: 'brand-gate-button' },
	},
};
```

The built-in placeholder carries `data-testid="consent-gate-placeholder"`,
its title `data-testid="consent-gate-title"` and its button
`data-testid="consent-gate-button"`. For a different design, pass your own
`placeholder` snippet.
