---
title: Embed
description: Embed external content like videos, maps, and custom HTML/JS in a sandboxed iframe.
icon: window-maximize
---

import { ComponentPreview } from '/snippets/component-preview.mdx'

`Embed` renders an iframe for external content. Pass a `url` for hosted content (YouTube, Google Maps, PDFs) or `html` for custom HTML/JS (Three.js scenes, Canvas visualizations, shader previews).

## Basic Usage

Any URL that works in an iframe works here. The first positional argument is `url`, so `Embed("https://...")` works as shorthand.

<Tabs>
<Tab title="Google Maps">
<ComponentPreview json={{"view":{"type":"Embed","url":"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d31313.45173960986!2d-77.07343327228305!3d38.903839376406246!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89b7b7b0dc3245fd%3A0xdfcb0cc37e2e6999!2s2112%20Pennsylvania%20Ave%20NW%2C%20Washington%2C%20DC%2020037!5e0!3m2!1sen!2sus!4v1772669563862!5m2!1sen!2sus","width":"100%","height":"400px"}}} playground=" ">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Embed

Embed(
    url=(
        "https://www.google.com/maps/embed?pb="
        "!1m18!1m12!1m3!1d31313.45173960986"
        "!2d-77.07343327228305!3d38.903839376406246"
        "!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1"
        "!3m3!1m2!1s0x89b7b7b0dc3245fd%3A0xdfcb0cc37e2e6999"
        "!2s2112%20Pennsylvania%20Ave%20NW%2C%20"
        "Washington%2C%20DC%2020037"
        "!5e0!3m2!1sen!2sus!4v1772669563862!5m2!1sen!2sus"
    ),
    width="100%",
    height="400px",
)
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "type": "Embed",
    "url": "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d31313.45173960986!2d-77.07343327228305!3d38.903839376406246!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89b7b7b0dc3245fd%3A0xdfcb0cc37e2e6999!2s2112%20Pennsylvania%20Ave%20NW%2C%20Washington%2C%20DC%2020037!5e0!3m2!1sen!2sus!4v1772669563862!5m2!1sen!2sus",
    "width": "100%",
    "height": "400px"
  }
}
```
</CodeGroup>
</ComponentPreview>
</Tab>
<Tab title="YouTube">
<ComponentPreview json={{"view":{"type":"Embed","url":"https://www.youtube.com/embed/iVdZdLL0CJ4","width":"100%","height":"400px"}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRW1iZWQKCkVtYmVkKAogICAgdXJsPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC9pVmRaZExMMENKNCIsCiAgICB3aWR0aD0iMTAwJSIsCiAgICBoZWlnaHQ9IjQwMHB4IiwKKQo=">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Embed

Embed(
    url="https://www.youtube.com/embed/iVdZdLL0CJ4",
    width="100%",
    height="400px",
)
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "type": "Embed",
    "url": "https://www.youtube.com/embed/iVdZdLL0CJ4",
    "width": "100%",
    "height": "400px"
  }
}
```
</CodeGroup>
</ComponentPreview>
</Tab>
<Tab title="Spotify">
<ComponentPreview json={{"view":{"type":"Embed","url":"https://open.spotify.com/embed/track/2CgBqLufSdwMt0FMIu1CVn?utm_source=generator","width":"100%","height":"152px"}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgRW1iZWQKCkVtYmVkKAogICAgdXJsPSgKICAgICAgICAiaHR0cHM6Ly9vcGVuLnNwb3RpZnkuY29tL2VtYmVkL3RyYWNrLyIKICAgICAgICAiMkNnQnFMdWZTZHdNdDBGTUl1MUNWbj91dG1fc291cmNlPWdlbmVyYXRvciIKICAgICksCiAgICB3aWR0aD0iMTAwJSIsCiAgICBoZWlnaHQ9IjE1MnB4IiwKKQo=">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Embed

Embed(
    url=(
        "https://open.spotify.com/embed/track/"
        "2CgBqLufSdwMt0FMIu1CVn?utm_source=generator"
    ),
    width="100%",
    height="152px",
)
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "type": "Embed",
    "url": "https://open.spotify.com/embed/track/2CgBqLufSdwMt0FMIu1CVn?utm_source=generator",
    "width": "100%",
    "height": "152px"
  }
}
```
</CodeGroup>
</ComponentPreview>
</Tab>
</Tabs>

## HTML Mode

For custom content like interactive visualizations, pass raw HTML via the `html` parameter. The content runs in an isolated iframe, so it can include `<script>` tags, Canvas, WebGL, or any client-side code:

<ComponentPreview json={{"view":{"type":"Embed","html":"<style>\n  html, body { margin: 0; height: 100%; overflow: hidden;\n               background: #1a1a2e; }\n  canvas { display: block; width: 100%; height: 100%;\n           cursor: crosshair; }\n</style>\n<canvas id=\"c\"></canvas>\n<script>\n  const canvas = document.getElementById('c');\n  const ctx = canvas.getContext('2d');\n  canvas.width = canvas.clientWidth;\n  canvas.height = canvas.clientHeight;\n  const particles = [];\n\n  canvas.addEventListener('mousemove', (e) => {\n    for (let i = 0; i < 3; i++) {\n      particles.push({\n        x: e.offsetX, y: e.offsetY,\n        vx: (Math.random() - 0.5) * 4,\n        vy: (Math.random() - 0.5) * 4,\n        life: 1,\n        hue: (Date.now() / 10) % 360,\n      });\n    }\n  });\n\n  function draw() {\n    ctx.fillStyle = 'rgba(26, 26, 46, 0.15)';\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    for (let i = particles.length - 1; i >= 0; i--) {\n      const p = particles[i];\n      p.x += p.vx; p.y += p.vy; p.life -= 0.015;\n      if (p.life <= 0) { particles.splice(i, 1); continue; }\n      ctx.beginPath();\n      ctx.arc(p.x, p.y, p.life * 4, 0, Math.PI * 2);\n      ctx.fillStyle =\n        `hsla(${p.hue}, 80%, 60%, ${p.life})`;\n      ctx.fill();\n    }\n    requestAnimationFrame(draw);\n  }\n  draw();\n</script>","width":"100%","height":"250px"}}} playground=" ">
<CodeGroup>
```python Python expandable icon="python"
from prefab_ui.components import Embed

Embed(
    html="""\
<style>
  html, body { margin: 0; height: 100%; overflow: hidden;
               background: #1a1a2e; }
  canvas { display: block; width: 100%; height: 100%;
           cursor: crosshair; }
</style>
<canvas id="c"></canvas>
<script>
  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d');
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  const particles = [];

  canvas.addEventListener('mousemove', (e) => {
    for (let i = 0; i < 3; i++) {
      particles.push({
        x: e.offsetX, y: e.offsetY,
        vx: (Math.random() - 0.5) * 4,
        vy: (Math.random() - 0.5) * 4,
        life: 1,
        hue: (Date.now() / 10) % 360,
      });
    }
  });

  function draw() {
    ctx.fillStyle = 'rgba(26, 26, 46, 0.15)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx; p.y += p.vy; p.life -= 0.015;
      if (p.life <= 0) { particles.splice(i, 1); continue; }
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.life * 4, 0, Math.PI * 2);
      ctx.fillStyle =
        `hsla(${p.hue}, 80%, 60%, ${p.life})`;
      ctx.fill();
    }
    requestAnimationFrame(draw);
  }
  draw();
</script>""",
    width="100%",
    height="250px",
)
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "type": "Embed",
    "html": "<style>\n  html, body { margin: 0; height: 100%; overflow: hidden;\n               background: #1a1a2e; }\n  canvas { display: block; width: 100%; height: 100%;\n           cursor: crosshair; }\n</style>\n<canvas id=\"c\"></canvas>\n<script>\n  const canvas = document.getElementById('c');\n  const ctx = canvas.getContext('2d');\n  canvas.width = canvas.clientWidth;\n  canvas.height = canvas.clientHeight;\n  const particles = [];\n\n  canvas.addEventListener('mousemove', (e) => {\n    for (let i = 0; i < 3; i++) {\n      particles.push({\n        x: e.offsetX, y: e.offsetY,\n        vx: (Math.random() - 0.5) * 4,\n        vy: (Math.random() - 0.5) * 4,\n        life: 1,\n        hue: (Date.now() / 10) % 360,\n      });\n    }\n  });\n\n  function draw() {\n    ctx.fillStyle = 'rgba(26, 26, 46, 0.15)';\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    for (let i = particles.length - 1; i >= 0; i--) {\n      const p = particles[i];\n      p.x += p.vx; p.y += p.vy; p.life -= 0.015;\n      if (p.life <= 0) { particles.splice(i, 1); continue; }\n      ctx.beginPath();\n      ctx.arc(p.x, p.y, p.life * 4, 0, Math.PI * 2);\n      ctx.fillStyle =\n        `hsla(${p.hue}, 80%, 60%, ${p.life})`;\n      ctx.fill();\n    }\n    requestAnimationFrame(draw);\n  }\n  draw();\n</script>",
    "width": "100%",
    "height": "250px"
  }
}
```
</CodeGroup>
</ComponentPreview>

## From Embed Code

Services like YouTube, Google Maps, and Spotify provide embed codes as raw `<iframe>` HTML. `Embed.from_iframe()` parses the tag and extracts `src`, `width`, `height`, `allow`, and `sandbox` automatically — just paste the snippet:

```python Pasting an embed code
from prefab_ui.components import Embed

Embed.from_iframe(
    '<iframe src="https://open.spotify.com/embed/track/2CgBqLufSdwMt0FMIu1CVn"'
    ' width="100%" height="352" allow="autoplay; encrypted-media;'
    ' fullscreen; picture-in-picture"></iframe>'
)
```

Bare numeric dimensions (like `height="352"`) are automatically converted to CSS values (`"352px"`). Any keyword arguments override the parsed attributes.

## Sandbox and Permissions

`Embed` exposes two standard HTML iframe attributes for controlling what the embedded content can do: `sandbox` and `allow`. These are independent of each other — `sandbox` restricts capabilities, `allow` grants access to browser APIs.

### sandbox

The HTML [`sandbox`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox) attribute restricts the iframe's capabilities. By default, no sandbox is applied — the iframe has full capabilities. Set `sandbox` to a space-separated list of permissions to restrict what the content can do:

```python Restricting capabilities
from prefab_ui.components import Embed

# Scripts can run, but no access to parent origin
Embed(url="https://example.com", sandbox="allow-scripts")

# Scripts + forms + same-origin access
Embed(
    url="https://example.com",
    sandbox="allow-scripts allow-same-origin allow-forms",
)
```

Common sandbox tokens: `allow-scripts`, `allow-same-origin`, `allow-forms`, `allow-popups`, `allow-modals`.

### allow

The HTML [`allow`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#allow) attribute controls which browser Permissions Policy features are available to the embedded content — things like fullscreen, autoplay, camera, microphone, and geolocation. Most embeds don't need this; it's primarily relevant for video embeds (fullscreen button) or content that accesses device APIs:

```python Feature policies
from prefab_ui.components import Embed

Embed(
    url="https://www.youtube.com/embed/dQw4w9WgXcQ",
    allow="fullscreen; autoplay",
)
```

## API Reference

<Card icon="code" title="Embed Parameters">
<ParamField body="url" type="str | None" default="None">
  URL to embed (iframe `src`). Mutually exclusive with `html`.
</ParamField>

<ParamField body="html" type="str | None" default="None">
  HTML content to embed (iframe `srcdoc`). Mutually exclusive with `url`.
</ParamField>

<ParamField body="width" type="str | None" default="None">
  CSS width value with units (e.g., `"100%"`, `"640px"`).
</ParamField>

<ParamField body="height" type="str | None" default="None">
  CSS height value with units.
</ParamField>

<ParamField body="sandbox" type="str | None" default="None">
  Iframe sandbox attribute. Space-separated list of permissions like `"allow-scripts allow-same-origin"`.
</ParamField>

<ParamField body="allow" type="str | None" default="None">
  Iframe allow attribute for feature policies like `"fullscreen; autoplay"`.
</ParamField>

<ParamField body="css_class" type="str | None" default="None">
  Additional Tailwind CSS classes.
</ParamField>
</Card>

## Protocol Reference

```json Embed
{
  "type": "Embed",
  "url?": "string",
  "html?": "string",
  "width?": "string",
  "height?": "string",
  "sandbox?": "string",
  "allow?": "string",
  "cssClass?": "string"
}
```

For the complete protocol schema, see [Embed](/protocol/embed).
