All skills
webflow avatar

/interactions

@dc4ba63 official
by webflowwebflow/webflow-skills126 stars
21

Create, update, list, and delete Webflow IX3 interactions (GSAP animations) through Webflow MCP. Use when the user wants click/hover/load/scroll/mouse-move animations, interaction timelines, or data_interactions_tool / create_interaction payloads.

  • 18 files
  • 218.5 KB
  • Updated last week
  • GitHub

Use this Skill: https://skilld.dev/gh/webflow/webflow-skills/interactions

This session only. Nothing lands on disk.

referencestrigger-mouse-move.md

≈2.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

<!-- Published from the Webflow monorepo: packages/systems/ix3/schema/agent-pack/references/trigger-mouse-move.md Do not edit here. Edit the source and re-publish. -->

wf:mouse-move

Read with envelope-and-targets.md.

controlType continuous (omit and the host stamps it)
Standalone Yes — must be the only trigger
Target [REQUIRED] in practice — validation accepts none, the runtime binds nothing. wf:viewport with value: '', or class/selector/attribute/inst
Roles [REQUIRED] on every timeline, unique
Playback [OMIT] all of control, delay, jump, speed

Send a target even though validation does not demand one

Mouse-move is absent from TRIGGER_REQUIRES_TARGET_KEYS, so a targetless payload passes every guard and saves cleanly. It then never fires.

bindTrigger only resolves elements when a target is present:

const targetSchema = trigger[2];
let elements: HTMLElement[] = [];
if (targetSchema) {
  elements = this.resolveTargets(targetSchema, {}, interaction);
}

ContinuousTriggerStrategy.bind iterates that list, so an empty one means the mouse-move handler is never invoked. There is no fallback to the viewport, body, or document: wf:viewport binds to window only because the handler checks for that extension key explicitly.

The same dead outcome applies when a target is present but resolves to nothing, for example wf:class with an empty value.

Send {extensionKey: 'wf:viewport', value: ''} unless you specifically want to bind to elements. Nothing will tell you otherwise: the write succeeds and the interaction is silently inert.

pluginConfig persist bounds

[REJECTED] smoothness outside its millisecond range, or restingState x/y outside 0 to 100. Absent keys are legal. Guard: findMouseMoveRangeError

The panel's Smoothness slider is 0 to 100 while the persisted bound follows the number input, so those two ranges differ. Current values are in capabilities.generated.md → Numeric bounds.

Roles

Exactly 'mouseX', 'mouseY', or 'interval'.

Role canvasDuration wf:mouse-follow action
mouseX / mouseY allowed allowed, at most one per timeline
interval not allowed not allowed

Accept

{
  pageId,
  name: 'Mouse',
  triggers: [{
    extensionKey: 'wf:mouse-move',
    config: {},  // optional pluginConfig: {restingState: {x: 0, y: 0}}
    target: {extensionKey: 'wf:viewport', value: ''},
  }],
  timelines: [
    {triggerMetadata: {role: 'mouseX'}, actions: [ACTION]},
    {triggerMetadata: {role: 'mouseY'}, actions: [ACTION2]},
  ],
}

wf:mouse-follow — the purpose-built action

Prefer this over wf:transform x/y when the user wants an element to track the cursor. The transform form works and is what SKILL.md Example 5 shows, but it hard-codes a travel range; wf:mouse-follow handles anchoring, leave behaviour, and axis pairing for you.

One action per axis, on the matching mouseX / mouseY timeline. Never on an interval timeline.

timelines: [
  {
    triggerMetadata: {role: 'mouseX'},
    actions: [{
      id: 'act-follow-x',
      name: 'Follow X',
      timing: {duration: 0.4},
      properties: {'wf:mouse-follow': {
        axis: 'x',
        followMode: 'full',
        leaveBehavior: 'stay',
        anchor: '0% 0%',        // cursor at the target's top-left; omit to centre
        groupId: 'mf-cursor',
        syncedActionId: 'act-follow-y',
      }},
      targets: [{extensionKey: 'wf:class', value: [STYLE_BLOCK_ID]}],
    }],
  },
  // …mirror for role 'mouseY' with axis 'y' and syncedActionId 'act-follow-x'
]

Values are bare scalars, not [from, to] pairs — there is no from-state to express. timing is still required, as on every action.

groupId here is not timeline group routing. Two different fields share the name: timeline.groupId routes a trigger via config.assignedGroupId (see timelines-and-groups.md), while wf:mouse-follow's groupId pairs the X and Y actions of one follow effect.

Shapes and defaults, from MouseFollowActionConfig:

Property Shape Default Notes
axis 'x' | 'y' derived Optional and not load-bearing — the runtime takes the authoritative axis from the timeline role (mouseX → x, mouseY → y). The store writes it for display only.
followMode 'full' | 'x-only' | 'y-only' 'full' 'full' keeps X and Y siblings in sync. Absence is treated as 'full', so pre-field data behaves unchanged.
leaveBehavior 'return' | 'stay' 'return' 'return' tweens back to the start position when the cursor leaves; 'stay' settles at the last position.
onEnter 'animate' | 'snap' — Behaviour on first enter and every re-enter. Reduced-motion users always snap regardless.
anchor "X% Y%" string, or an ix3-origin object "50% 50%" Anchor point of the target relative to the cursor. Omitted centres the target on the cursor.
groupId string — Pairs the X and Y actions of one effect.
syncedActionId string — The sibling action this one is mirrored with.

followMode is mirrored between paired actions so both sides agree; setting 'x-only' or 'y-only' on a pair tells the editor's exit reconciler to delete the redundant sibling.

The trigger's own pluginConfig also carries a resting state for when the pointer is idle or has left: per-axis percentages 0–100, defaulting to {x: 50, y: 50} (centre).

Interval metadata

distance and axes are authored only on an interval timeline.

Shapes:

Field Shape Bounds
distance integer finite, 1–10000
axes {x?: boolean, y?: boolean} per-axis enable bits

axes is a shared shape for continuous triggers rather than a mouse-move invention — any plugin driving 2D channels uses the same object instead of re-implementing it in pluginConfig. Both fields live at timeline.triggerMetadata, beside role.

triggerMetadata: {role: 'interval', distance: 100, axes: {x: true, y: true}}

[REJECTED] Either field on a timeline whose role is not interval. The interval editor is the only place the Designer writes them. Guard: findOffIntervalMetadataError · fragment: only authored on interval mouse-move timelines

[LEGACY-OK-ON-UPDATE] An unchanged echo of a stored value on the same timeline id passes, so a duration or target read-modify-write is not forced to strip it.

[REJECTED] Interval metadata on an interaction with no wf:mouse-move trigger. Guard: findIntervalMetadataTriggerError · fragment: not write them without a "wf:mouse-move" trigger

[LEGACY-OK-ON-UPDATE] Already-stranded stored data passes: if the stored triggers also lacked mouse-move and the same timeline already carried an interval role with distance or axes, the update is not introducing the mismatch and is allowed. The panel gates its interval editor on the role alone, so a user can still edit those fields on a stranded pair.

Two writes still reject: removing the mouse-move trigger from an interaction that has interval metadata, and newly attaching those fields to an interaction that has no mouse-move trigger.

[REJECTED] A distance that is fractional, or outside 1 to 10000. Bounded in triggerMetadata's schema as z.number().finite().int().min(1).max(10000), so this fails schema validation before any guard runs. The bound exists to keep a malicious payload from persisting Infinity or NaN into storage; the runtime caps fires per update regardless.

Rejected

[REJECTED] A missing, duplicate, or invalid role on any timeline. Guard: findTimelineRoleError · fragment: Expected one of: mouseX, mouseY, interval

[REJECTED] Combined with any other trigger. Guard: findStandaloneTriggerError

[REJECTED] Any of control, delay, jump, speed. Guard: findUneditablePlaybackFieldError

[REJECTED] wf:viewport as a target on any trigger other than mouse-move.

[REJECTED] A wf:mouse-follow action outside a mouseX/mouseY timeline, or more than one per timeline. Guard: findMouseFollowContextError

[REJECTED] conditionalLogic — conditions are unavailable and continuous triggers are additionally excluded. Guards: findConditionsCapabilityError, findContinuousConditionsError

[REJECTED] conditionalPlayback with behavior: 'skip-to-end' while a continuous trigger is present. Use dont-animate. Guard: findConditionalPlaybackError

Panel trap

[PANEL-TRAP] timing.repeat and timing.yoyo on a continuous interaction. The panel hides both controls when a continuous trigger is present, and findScrollScrubActionTimingError keys off scrub rather than continuous, so nothing rejects them.

They are not inert. buildTweensForAction forwards a finite repeat and any yoyo straight into the GSAP tween vars, and repeat extends the timeline duration that continuous scrubbing maps gesture progress across. So a value set here changes how the interaction plays while remaining invisible and uneditable in the panel, which is the worst combination in this class.

One special case: on a percent canvas an infinite repeat: -1 is materialized to a single cycle (0), because an infinite duration would break the 0 to 1 scrub mapping. Outside a percent canvas, -1 passes through unchanged.

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at dc4ba63. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last week
version
2026.09.21

README badge

README badge for webflow/webflow-skills/interactions