---
title: Embeds
description: Hold YouTube videos, maps and other iframes on a plain HTML page
  until their consent category is allowed with data-src and data-category, show
  a placeholder, and configure the c15t iframe blocker.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Gate an iframe

Move the iframe's URL from `src` to `data-src` and name its category in
`data-category`. The c15t script tag gives the iframe its `src` once the
category is allowed:

```html title="index.html"
<iframe
	data-src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
	data-category="measurement"
	title="YouTube video"
	allow="encrypted-media; picture-in-picture"
	allowfullscreen
></iframe>
```

An iframe with no `src` loads nothing, so the vendor gets no request and sets
no cookie until the visitor allows the category. Give every embed a `title`
that says what it shows.

## Attributes

|Attribute|What it does|
|--|--|
|`data-src`|The embed's URL. c15t moves it to `src` when the gate opens. Only `http:` and `https:` URLs load. A relative URL resolves against the page.|
|`data-category`|One category name. An unknown name logs a warning and keeps the iframe blocked.|
|`data-vendor`|Optional vendor ID for vendor-level consent. The iframe also stays blocked while the visitor has turned that vendor off. See [vendor consent](/docs/frameworks/html/vendor-consent).|
|`data-c15t-paused`|Set by c15t when it took away a `src` the iframe already had. Do not set it yourself.|

Iframes without `data-category` or `data-vendor` are left alone.

## Show a placeholder

c15t does not draw anything in place of a blocked iframe. Put your own message
next to it and hide it once the iframe has a `src`:

```html
<div class="embed">
  <iframe data-src="…" data-category="measurement" title="YouTube video"></iframe>
  <div class="placeholder">
    <p>Allow measurement to load this YouTube video.</p>
    <a href="#c15t-preferences">Choose video permissions</a>
  </div>
</div>
```

```css
.embed iframe:not([src]) { display: none; }
.embed iframe[src] + .placeholder { display: none; }
```

The `#c15t-preferences` link opens the preference dialog, where the visitor
can allow the category. See [preferences link](/docs/frameworks/html/components/preferences-link).

## What happens on withdrawal

When a visitor turns the category off, c15t removes the iframe's `src` and puts
the URL back in `data-src`, which unloads the embed. The page also reloads by
default, as it does for scripts.

An iframe written with a normal `src` and a `data-category` starts loading
before c15t runs, then c15t removes the `src`. The vendor already got its
request, so always use `data-src`.

## Iframes added later

c15t watches the whole document. An iframe that a page builder, a CMS widget
or your own script adds later is gated the moment it appears, and so is an
iframe whose `data-category` changes. This keeps working when a client router
such as Turbo replaces `<body>` on navigation, and when the c15t tag runs in
`<head>` before `<body>` exists.

## Configure the iframe blocker

The iframe blocker is on by default in every `@c15t/browser` build. Set
`iframeBlocker` in a queued `config` call to change it:

```html
<script>
  window.c15t = window.c15t || [];
  c15t.push(['config', { iframeBlocker: false }]);
</script>
```

|Value|Effect|
|--|--|
|omitted or `{}`|Gate every iframe with `data-category` or `data-vendor`, including ones added later.|
|`{ disableAutomaticBlocking: true }`|Do not scan or watch the page. c15t sets or removes a `src` only when you call `c15t.processIframes()`. See [check iframes yourself](#check-iframes-yourself).|
|`false`|Turn the blocker off. `data-src` iframes never load.|

The categories on gated iframes are added to the preference dialog, as they
are for gated scripts.

## Check iframes yourself

With `iframeBlocker: { disableAutomaticBlocking: true }`, c15t leaves iframes
alone until your page calls `c15t.processIframes()`. Each call pauses gated
iframes, those with `data-category` or `data-vendor`, that consent does not
allow, and restores the ones it does. Call it after the policy resolves, after
you add iframes, and after consent changes:

```html
<script>
  window.c15t = window.c15t || [];
  c15t.push(['config', { iframeBlocker: { disableAutomaticBlocking: true } }]);
  c15t.push(['processIframes']);
  c15t.push(['on', 'consent', () => c15t.processIframes()]);
</script>
```

A queued `processIframes` runs once the policy has resolved, so
`c15t.push(['processIframes'])` is safe before and after the tag loads. With
automatic blocking on, c15t does this by itself and you do not need to call
it.

## Vendor embeds

The [integration guides](/docs/integrations/overview) have an HTML tab for
YouTube, Google Maps and other embeds, with the category each one needs. The
`youtube-nocookie.com` player still contacts Google when it loads, so gate it
like any other embed.

## Check it works

Open the page in a private window with the Network tab open.

1. Before you choose, the iframe has no `src` in the Elements panel and there
   is no request to the embed's host. The placeholder shows.
2. Allow the category. The iframe gets its `src`, the embed loads and the
   placeholder hides.
3. Open preferences and turn the category off. The page reloads and the embed
   stays unloaded.
