> This page is for Designer API.

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

# Interaction examples

> Complete webflow.interactions create calls for click, hover, page load, scroll, mouse move, and custom event triggers.

A complete `create` call for each trigger type. For every field and value, see [Interaction schema](/designer/reference/interaction-schema).

Each example assumes the site has exactly one `hero-btn` class, and limits the interaction to the current page.

## Click

**`Fade in on click`**

```typescript title="Fade in on click"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Click fade',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:click',
      config: {control: 'play'},
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
  ],
  timelines: [
    {
      name: 'Fade',
      actions: [
        {
          id: 'click-fade',
          name: 'Fade',
          tt: 2,
          timing: {duration: 0.4, ease: 2},
          properties: {'wf:transform': {opacity: ['0%', '100%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

A from-to fade that starts at `0%` keeps the element hidden until the first click.

## Hover

Two triggers, one for enter and one for leave. Each routes to its own timeline with `assignedGroupId`.

**`Fade in on enter, fade out on leave`**

```typescript title="Fade in on enter, fade out on leave"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Hover fade',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:hover',
      config: {
        control: 'play',
        assignedGroupId: 'hover-in',
        pluginConfig: {multiTimeline: false, eventMode: 'enter'},
      },
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
    {
      extensionKey: 'wf:hover',
      config: {
        control: 'play',
        assignedGroupId: 'hover-out',
        pluginConfig: {multiTimeline: false, eventMode: 'leave'},
      },
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
  ],
  timelines: [
    {
      name: 'Enter',
      groupId: 'hover-in',
      actions: [
        {
          id: 'hover-in',
          name: 'Fade in',
          tt: 2,
          timing: {duration: 0.3, ease: 2},
          properties: {'wf:transform': {opacity: ['0%', '100%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
    {
      name: 'Leave',
      groupId: 'hover-out',
      actions: [
        {
          id: 'hover-out',
          name: 'Fade out',
          tt: 2,
          timing: {duration: 0.3, ease: 2},
          properties: {'wf:transform': {opacity: ['100%', '0%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

The same result with one trigger and two timeline roles:

**`One trigger with two roles`**

```typescript title="One trigger with two roles"
triggers: [
  {
    extensionKey: 'wf:hover',
    config: {control: 'play', pluginConfig: {multiTimeline: true}},
    target: {extensionKey: 'wf:class', value: 'hero-btn'},
  },
],
timelines: [
  {name: 'Enter', triggerMetadata: {role: 'mouseEnter'}, actions: [/* fade in */]},
  {name: 'Leave', triggerMetadata: {role: 'mouseLeave'}, actions: [/* fade out */]},
],
```

## Page load

**`Fade in on page load`**

```typescript title="Fade in on page load"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Load fade',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:load',
      config: {control: 'play'},
    },
  ],
  timelines: [
    {
      name: 'Fade',
      actions: [
        {
          id: 'load-fade',
          name: 'Fade',
          tt: 2,
          timing: {duration: 0.6, ease: 2},
          properties: {'wf:transform': {opacity: ['0%', '100%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

## Scroll

Play once as the element enters the viewport. The from tween (`tt: 1`) starts the element hidden.

**`Reveal once on scroll`**

```typescript title="Reveal once on scroll"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Scroll reveal',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:scroll',
      config: {
        scrollTriggerConfig: {
          start: 'top 90%',
          end: 'bottom 15%',
          enter: 'play',
          leaveBack: 'reset',
        },
      },
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
  ],
  timelines: [
    {
      name: 'Reveal',
      actions: [
        {
          id: 'scroll-reveal',
          name: 'Reveal',
          tt: 1,
          timing: {duration: 0.6, ease: 2},
          properties: {'wf:transform': {opacity: ['0%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

Scrub with the scroll position. A scrub needs `canvasDuration: 1` on the timeline and `duration: 1` on the action.

**`Scrub a fade with the scroll position`**

```typescript title="Scrub a fade with the scroll position"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Scroll scrub fade',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:scroll',
      config: {
        scrollTriggerConfig: {
          start: 'top bottom',
          end: 'top 10%',
          scrub: 0.3,
        },
      },
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
  ],
  timelines: [
    {
      name: 'Scrub',
      canvasDuration: 1,
      actions: [
        {
          id: 'scroll-scrub',
          name: 'Fade',
          tt: 2,
          timing: {duration: 1, ease: 2},
          properties: {
            'wf:transform': {opacity: ['0%', '100%'], xPercent: ['-40%', '0%']},
          },
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

## Mouse move

`wf:viewport` tracks the cursor across the whole page. Each timeline has its own role.

**`Move an element with the cursor`**

```typescript title="Move an element with the cursor"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Mouse follow',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:mouse-move',
      config: {},
      target: {extensionKey: 'wf:viewport', value: ''},
    },
  ],
  timelines: [
    {
      name: 'Mouse X',
      triggerMetadata: {role: 'mouseX'},
      actions: [
        {
          id: 'mouse-x',
          name: 'Follow X',
          tt: 2,
          timing: {duration: 0.4},
          properties: {'wf:transform': {x: ['0px', '40px']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
    {
      name: 'Mouse Y',
      triggerMetadata: {role: 'mouseY'},
      actions: [
        {
          id: 'mouse-y',
          name: 'Follow Y',
          tt: 2,
          timing: {duration: 0.4},
          properties: {'wf:transform': {y: ['0px', '40px']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

## Custom event

**`Play on a custom event`**

```typescript title="Play on a custom event"
const page = await webflow.getCurrentPage();

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Custom event flash',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:custom',
      config: {
        control: 'play',
        pluginConfig: {eventName: 'my-event'},
      },
      target: {extensionKey: 'wf:body', value: ''},
    },
  ],
  timelines: [
    {
      name: 'Flash',
      actions: [
        {
          id: 'custom-flash',
          name: 'Flash',
          tt: 2,
          timing: {duration: 0.4, ease: 2},
          properties: {'wf:transform': {opacity: ['100%', '20%']}},
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

Fire the event from code on the published page, after interactions load. A DOM event does not play it.

**`Fire the event`**

```javascript title="Fire the event"
const interactionsApi = Webflow.require('ix3');
interactionsApi.emit('my-event');
```

## Class change

A Set action (`tt: 3`) that adds a class. `selectors` takes style block ids, not class slugs.

**`Add a class on click`**

```typescript title="Add a class on click"
const page = await webflow.getCurrentPage();
const style = await webflow.getStyleByName('is-active');
if (!style) throw new Error('Create the is-active class first.');

const created = await webflow.interactions.create({
  pageId: page.id,
  name: 'Add class on click',
  scope: {type: 'pages', value: [page.id]},
  triggers: [
    {
      extensionKey: 'wf:click',
      config: {control: 'play'},
      target: {extensionKey: 'wf:class', value: 'hero-btn'},
    },
  ],
  timelines: [
    {
      actions: [
        {
          id: 'add-class',
          name: 'Add class',
          tt: 3,
          timing: {},
          properties: {
            'wf:class': {class: {operation: 'addClass', selectors: [style.id]}},
          },
          targets: [{extensionKey: 'wf:class', value: 'hero-btn'}],
        },
      ],
    },
  ],
});
```

## When a saved interaction does not play

* A custom event trigger has no `eventName`, or the page dispatches a DOM event instead of calling `emit`.
* The action has `hidden: true`.
* A from-to fade starts at `0%`, so the element stays hidden until the trigger fires. This is expected.