Skip to main content

React Components

ConsentGate

ConsentGate mounts its children only while the effective permission for one consent category is granted, and shows a placeholder otherwise. Wrap any iframe or third-party widget that would set cookies or contact a vendor on load. Render it anywhere inside the ConsentProvider from your quickstart, which also mounts the ConsentDialog the placeholder button opens.

src/product-video.tsx
import { ConsentGate } from 'c15t/react';

export function ProductVideo() {
	return (
		<ConsentGate category="marketing" className="video-frame">
			<iframe
				src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
				title="Product tour"
				loading="lazy"
				allowFullScreen
				style={{ width: '100%', aspectRatio: '16 / 9', border: 0 }}
			/>
		</ConsentGate>
	);
}

marketing must be in your policy scope. If you set a non-empty options.consentCategories list on ConsentProvider, include marketing there too so the preference center can display and save it. ConsentGate does not add categories to that list.

The placeholder names the category using its title from your translations. Set className or style on ConsentGate to reserve the embed's space, so the page does not shift when the placeholder is replaced.

Props

PropTypeDefaultDescription
categoryAllConsentNamesrequiredThe consent category whose effective permission gates the children, for example 'marketing' or 'functionality'.
childrenReactNoderequiredThe embed. It is not mounted until the category is allowed, so it makes no requests before permission.
placeholderReactNodebuilt-in placeholderReplaces the built-in title and button while the category is not allowed. Falsy values such as null, false, 0 and an empty string use the built-in placeholder. Pass an empty fragment, <></>, to render no placeholder content.

Other div attributes such as className, style and ref apply to the wrapper element that stays in the page in both states. ConsentGateProps also declares noStyle and theme, but the component does not apply them yet; style the wrapper with className, and style the built-in placeholder through its parts in the provider options.

Behavior

ConsentGate renders a div wrapper. Inside it, when the effective permission for category is granted, it renders the children; otherwise it renders the placeholder. Permission is the same value useConsent(category) returns, so it can be granted under an opt-out rule before the visitor records a choice, and a recorded grant can be overridden by a privacy signal.

While effective permission is denied, the children are absent from the DOM, so an iframe or a third-party widget inside ConsentGate sends no requests. When the visitor later revokes it, the children unmount and the embed disappears. ConsentGate does not depend on the network blocker or the iframe blocker modules; use those for markup you cannot wrap in a component.

The built-in placeholder shows the title consentGate.title, Accept {category} consent to view this content., with {category} replaced by consentTypes.<category>.title, and a button labelled consentGate.actionButton, Enable {category} consent. The button opens the preference center; it does not grant the category by itself, because the visitor still has to save. Mount a ConsentDialog in the same provider so the button has something to open.

Under a policy with scopeMode: 'strict', a category outside the rule's scope cannot be granted. The built-in placeholder then shows consentGate.policyBlocked, "This content is unavailable under your region's consent policy.", without the button, and a stored grant for that category stays blocked.

When the provider starts from a snapshot prefetched on the server, the server renders the placeholder for a denied category. For a granted category it renders the empty wrapper, and the children mount once hydration completes, so an iframe loads once even when the page streams inside a Suspense boundary. A grant in the request cookie allows the embed only when policy and privacy signals permit it. Browser privacy signals detected during hydration can withdraw that permission. In a browser-only setup the provider has no permission until it resolves policy on the client, so ConsentGate shows the placeholder first and swaps in the embed after resolution when the category is already granted.

ConsentGate is one component on this page; the per-vendor embed guides for YouTube and Google Maps show a complete embed configuration with sizing, titles and the same ConsentGate usage across frameworks.

To read the same permission in your own components, use useConsent from c15t/react; the useIframeBlocker and useNetworkBlocker module hooks also import from there.

Composition

The placeholder parts are available as ConsentGate.Root, ConsentGate.Title and ConsentGate.Button for a custom placeholder that keeps the built-in copy and behavior. ConsentGate.Title and ConsentGate.Button accept category and fill in the translated text; pass children to either to replace it.

<ConsentGate
  category="marketing"
  placeholder={
    <ConsentGate.Root>
      <ConsentGate.Title category="marketing" />
      <p>The video is also available on our channel.</p>
      <ConsentGate.Button category="marketing">Choose cookies</ConsentGate.Button>
    </ConsentGate.Root>
  }
>
  <iframe src="https://www.youtube-nocookie.com/embed/..." title="Product tour" />
</ConsentGate>

To restyle the built-in placeholder without replacing it, set its parts under components['consent-gate'] in the provider options. root is the card, and title and button are its text and button. The consentGate, consentGateTitle and consentGateButton theme slots reach the same parts. The button part applies on top of button.primary.

components: {
  'consent-gate': {
    root: { className: 'rounded-none' },
    button: { className: 'font-semibold' },
  },
},

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".

Accessibility

The placeholder is plain text and a button, so it is readable and operable without the embed. Give the iframe a descriptive title, and keep a transcript, address or link outside the ConsentGate for visitors who decline the category. A denied category may be fixed by policy, so opening the preference center does not guarantee that the visitor can grant it.

Verify

Use an optional in-scope category that is available in the preference center and is not restricted by policy or privacy signals such as GPC. Load the page with the category denied. The placeholder text names the category and the network panel shows no request to the embed's host. With a prefetched snapshot, the server HTML contains the placeholder for a denied category and neither placeholder nor embed for a granted category; the embed appears after hydration and the network panel shows one request for its document. Browser-only initialization shows the placeholder before policy resolves, then replaces it with the embed if the category is granted. After policy resolves, activate the placeholder button: the preference center opens. Turn the category on and Save: the placeholder is replaced by the embed and its requests start. Reject the category again from the preference center: the embed disappears.