> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://typeform.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://typeform.ferndocs.com/_mcp/server.

# Vanilla Embed Library

> Learn how to embed typeforms in any website using the vanilla JavaScript embed library with full customization options.

The Vanilla Embed Library lets you embed typeforms directly into your website using pure JavaScript, without any framework dependencies. It works with any website or framework.

## Installation

### Via CDN (quickest method)

Add the script tag to your HTML:

```html
<script src="https://embed.typeform.com/next/embed.js"></script>
```

### Via NPM

For build-tool projects:

```bash
npm install @typeform/embed
```

Then import in your JavaScript:

```javascript
import {
  createWidget,
  createPopup,
  createSlider,
  createSidetab,
} from '@typeform/embed';
```

## Embed types

Typeform offers four main embed types:

1. **Widget** (Inline): Embedded directly in your page
2. **Popup**: Opens in a modal overlay
3. **Slider**: Slides in from the side
4. **Sidetab**: Persistent tab on the side of the page

## Widget (Inline) embed

Embed the form directly into your page content.

### Basic implementation

```html
<div id="my-typeform"></div>

<script>
  const { createWidget } = window.tf.createWidget('YOUR_FORM_ID', {
    container: document.getElementById('my-typeform'),
    height: 500,
  });
</script>
```

### With options

```html
<div id="my-typeform" style="height: 500px;"></div>

<script>
  const { createWidget } = window.tf.createWidget('YOUR_FORM_ID', {
    container: document.getElementById('my-typeform'),
    height: 500,
    hidden: {
      user_id: '12345',
      source: 'website',
    },
    onReady: () => {
      console.log('Form is ready');
    },
    onSubmit: () => {
      console.log('Form submitted');
    },
  });
</script>
```

## Popup embed

Display the form in a modal overlay.

### Trigger on button click

```html
<button id="open-typeform">Take Survey</button>

<script>
  const { open, close, toggle } = window.tf.createPopup('YOUR_FORM_ID', {
    autoClose: 3000, // Close 3 seconds after submission
    hidden: {
      user_id: '12345',
    },
    onSubmit: () => {
      console.log('Form submitted');
    },
  });

  document.getElementById('open-typeform').addEventListener('click', () => {
    open();
  });
</script>
```

### Auto-open on page load

```html
<script>
  window.tf.createPopup('YOUR_FORM_ID', {
    autoOpen: true,
    autoClose: 3000,
  });
</script>
```

### Auto-open with delay

```html
<script>
  window.tf.createPopup('YOUR_FORM_ID', {
    autoOpen: 5000, // Open after 5 seconds
  });
</script>
```

## Slider embed

Slide in from the right, left, top, or bottom of the screen.

```html
<button id="open-slider">Get Started</button>

<script>
  const { open, close, toggle } = window.tf.createSlider('YOUR_FORM_ID', {
    position: 'right', // 'right', 'left', 'top', 'bottom'
    width: 500,
    height: 600,
    hidden: {
      source: 'slider',
    },
  });

  document.getElementById('open-slider').addEventListener('click', () => {
    open();
  });
</script>
```

## Sidetab embed

Display a persistent tab that opens the form when clicked.

```html
<script>
  window.tf.createSidetab('YOUR_FORM_ID', {
    buttonText: 'Feedback',
    position: 'right', // 'right' or 'left'
    hidden: {
      page: window.location.pathname,
    },
  });
</script>
```

## Configuration options

### Common options (all embed types)

| Option              | Type     | Default               | Description                              |
| ------------------- | -------- | --------------------- | ---------------------------------------- |
| `hidden`            | object   | `{}`                  | Hidden fields to pass to the form        |
| `tracking`          | object   | `{}`                  | Tracking parameters (UTM, etc.)          |
| `domain`            | string   | `'form.typeform.com'` | Custom domain for white-label forms      |
| `onReady`           | function | `null`                | Callback when form is ready              |
| `onSubmit`          | function | `null`                | Callback when form is submitted          |
| `onClose`           | function | `null`                | Callback when form is closed             |
| `onQuestionChanged` | function | `null`                | Callback when question changes           |
| `onHeightChanged`   | function | `null`                | Callback when height changes             |
| `autoFocus`         | boolean  | `false`               | Auto-focus the form on load              |
| `lazy`              | boolean  | `false`               | Lazy load the form                       |
| `keepSession`       | boolean  | `false`               | Keep session for partial submissions     |
| `hideHeaders`       | boolean  | `false`               | Hide Typeform branding (PRO feature)     |
| `hideFooter`        | boolean  | `false`               | Hide footer (PRO feature)                |
| `opacity`           | number   | `100`                 | Background opacity (0-100)               |
| `disableScroll`     | boolean  | `false`               | Disable page scrolling when form is open |

### Widget-specific options

| Option           | Type          | Default  | Description                  |
| ---------------- | ------------- | -------- | ---------------------------- |
| `container`      | HTMLElement   | `null`   | Container element (required) |
| `height`         | number        | `500`    | Height in pixels             |
| `width`          | number/string | `'100%'` | Width (px or %)              |
| `inlineOnMobile` | boolean       | `false`  | Force inline on mobile       |

### Popup-specific options

| Option      | Type           | Default | Description                        |
| ----------- | -------------- | ------- | ---------------------------------- |
| `autoOpen`  | boolean/number | `false` | Auto-open on load (or delay in ms) |
| `autoClose` | number         | `null`  | Auto-close after submission (ms)   |
| `size`      | number         | `100`   | Size as percentage (50-100)        |
| `width`     | number         | `null`  | Fixed width in pixels              |
| `height`    | number         | `null`  | Fixed height in pixels             |

### Slider-specific options

| Option     | Type   | Default   | Description                                |
| ---------- | ------ | --------- | ------------------------------------------ |
| `position` | string | `'right'` | Position: 'left', 'right', 'top', 'bottom' |
| `width`    | number | `800`     | Width in pixels                            |
| `height`   | number | `600`     | Height in pixels                           |

### Sidetab-specific options

| Option            | Type   | Default       | Description                 |
| ----------------- | ------ | ------------- | --------------------------- |
| `buttonText`      | string | `'Launch me'` | Text on the tab             |
| `position`        | string | `'right'`     | Position: 'left' or 'right' |
| `buttonColor`     | string | `null`        | Custom button color         |
| `buttonTextColor` | string | `null`        | Custom text color           |
| `customIcon`      | string | `null`        | Custom icon URL             |

## Methods

All embed types return methods for programmatic control:

```javascript
const { open, close, toggle, refresh, focus, unmount } =
  window.tf.createPopup('YOUR_FORM_ID');

// Open the form
open();

// Close the form
close();

// Toggle (open if closed, close if open)
toggle();

// Refresh/reload the form
refresh();

// Focus on the form
focus();

// Remove the form from DOM
unmount();
```

## Callbacks

### onReady

Triggered when the form is fully loaded:

```javascript
window.tf.createWidget('YOUR_FORM_ID', {
  container: document.getElementById('form'),
  onReady: ({ formId }) => {
    console.log(`Form ${formId} is ready`);
  },
});
```

### onSubmit

Triggered when the form is submitted:

```javascript
window.tf.createPopup('YOUR_FORM_ID', {
  onSubmit: ({ formId, responseId }) => {
    console.log(`Form ${formId} submitted`);
    console.log(`Response ID: ${responseId}`);

    // Send to analytics
    gtag('event', 'form_submit', {
      form_id: formId,
    });
  },
});
```

### onClose

Triggered when a popup/slider/sidetab is closed:

```javascript
window.tf.createPopup('YOUR_FORM_ID', {
  onClose: () => {
    console.log('Form closed');
  },
});
```

### onQuestionChanged

Triggered when the respondent moves to a new question:

```javascript
window.tf.createWidget('YOUR_FORM_ID', {
  container: document.getElementById('form'),
  onQuestionChanged: ({ formId, ref }) => {
    console.log(`Question changed to: ${ref}`);
  },
});
```

### onHeightChanged

Triggered when the form height changes (useful for responsive iframes):

```javascript
window.tf.createWidget('YOUR_FORM_ID', {
  container: document.getElementById('form'),
  onHeightChanged: ({ height }) => {
    console.log(`Form height changed to: ${height}px`);
  },
});
```

## Complete example

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Typeform Embed Example</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
      }
      #typeform-container {
        max-width: 800px;
        margin: 0 auto;
        height: 600px;
      }
      .button-group {
        text-align: center;
        margin: 20px 0;
      }
      button {
        background: #6c5ce7;
        color: white;
        border: none;
        padding: 12px 24px;
        font-size: 16px;
        cursor: pointer;
        margin: 0 10px;
        border-radius: 4px;
      }
      button:hover {
        background: #5b4bc4;
      }
    </style>
  </head>
  <body>
    <h1>Typeform Embed Demo</h1>

    <div class="button-group">
      <button id="open-popup">Open Popup</button>
      <button id="open-slider">Open Slider</button>
    </div>

    <div id="typeform-container"></div>

    <script src="https://embed.typeform.com/next/embed.js"></script>
    <script>
      // Widget embed
      window.tf.createWidget('YOUR_FORM_ID', {
        container: document.getElementById('typeform-container'),
        height: 600,
        hidden: {
          user_id: 'demo_user',
          source: 'website',
        },
        onReady: () => {
          console.log('Widget ready');
        },
        onSubmit: ({ responseId }) => {
          console.log('Form submitted:', responseId);
          alert('Thank you for your submission!');
        },
      });

      // Popup embed
      const { open: openPopup } = window.tf.createPopup('YOUR_FORM_ID', {
        autoClose: 3000,
        size: 80,
        hidden: {
          source: 'popup',
        },
      });

      document
        .getElementById('open-popup')
        .addEventListener('click', openPopup);

      // Slider embed
      const { open: openSlider } = window.tf.createSlider('YOUR_FORM_ID', {
        position: 'right',
        width: 600,
        hidden: {
          source: 'slider',
        },
      });

      document
        .getElementById('open-slider')
        .addEventListener('click', openSlider);

      // Sidetab embed
      window.tf.createSidetab('YOUR_FORM_ID', {
        buttonText: 'Feedback?',
        position: 'right',
        hidden: {
          source: 'sidetab',
          page: window.location.pathname,
        },
      });
    </script>
  </body>
</html>
```

## Next steps

* Learn about [inline embeds](/embed/vanilla/inline)
* Explore [modal embeds](/embed/vanilla/modal)
* See full [configuration options](/embed/vanilla/configuration)
* Check [URL parameters](/embed/vanilla/url-parameters) for dynamic content
* Implement [callbacks](/embed/vanilla/callbacks) for tracking