TanStack Start Components
ConsentGate
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 contacts a vendor when
it loads:
With an awaited root loader, the server HTML already contains the placeholder or, for a visitor who allowed the category, the iframe. The category must be in your policy's scope.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
category | AllConsentNames | required | The consent category whose effective permission gates the children, for example 'marketing' or 'functionality'. |
children | ReactNode | required | The embed. It is not mounted until the category is allowed, so it makes no requests before permission. |
placeholder | ReactNode | built-in placeholder | Replaces 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.
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.
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.
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.