---
title: Consent Gate
description: "`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."
lastModified: "2026-10-10T14:59:21+01:00"
lastAuthor: Kaylee
---
## 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.
