Use when user asks to add/edit motion without specific library, or with @wix/interact - hover, click, view/scroll triggered, scroll-driven, and pointer-driven animations for web. Wire animations to user interactions; install or set up @wix/interact; or edit an existing interact config. Do NOT use for other animation libraries.
Interactor — build interactions with @wix/interact
This skill installs, wires up, and configures motion interactions so you can
add or edit interactions on any webpage or web app. It is interact-first: you
describe what should animate and when as a declarative JSON config, and the
library does the DOM wiring. You almost never call the motion engine directly.
Mental model — packages and one config
Package
Role
You touch it…
@wix/interact
Declarative layer. Binds triggers → effects via an InteractConfig. Ships vanilla / React / Web-Component entry points.
Always. This is the API.
@wix/motion-presets
Ready-made named effects (entrance, scroll, ongoing, mouse). Referenced as namedEffect: { type: 'FadeIn' }.
When you want a prebuilt effect (the common case).
@wix/motion
The engine (WAAPI, CSS, ViewTimeline, fastdom). Bundled inside interact.
Rarely — only for programmatic/escape-hatch animation. See references/motion-engine.md.
@wix/interact-validate
Static validator for InteractConfig shape (schema + referential checks). No DOM.
Agent-side validation always; optional dev/CI guard in user projects. See references/validate.md.
@wix/splittext
Splits text into per-char/word/line spans. Ships an Interact adapter at @wix/splittext/plugin.
When animating text per character, word, or line. See references/plugins.md.
The whole job is: pick a trigger, pick an effect, bind it to an element with a
key. Everything else is detail.
Follow four steps in order: Install → Integrate → Add/Edit interactions → Validate. If
the project already uses interact (a config and the package exist), skip to
Add/Edit. Read the linked reference files as you reach each step — they hold the
full schema, the preset catalog, and per-trigger rules. Don't try to hold it all
in your head; the references are the source of truth.
For static or pre-rendered output (agent-authored HTML, SSG, static export),
follow the canonical CSS generation policy in
references/integration-recipes.md.
Step 1 — Install
Both packages, one command. @wix/motion comes transitively inside
@wix/interact — do not install it separately on this path.
npm install @wix/interact @wix/motion-presets
# (yarn add / pnpm add work too — match the project's package manager)
npm install @wix/splittext # only for per-char/word/line text animation; not transitive
npm install -D @wix/interact-validate # optional — permanent dev/CI config guard only
A no-build / plain-HTML site can skip npm and import Interact from a CDN for
runtime wiring — see the CDN recipe in references/integration-recipes.md.
CDN pages skip the validate package install; the agent validates configs without
shipping the validator.
Step 2 — Integrate
First, detect the stack and pick the entry point (this determines every
import and a couple of flags). Decision procedure:
React / Next / any JSX project (a package.json with react, .jsx/.tsx files) → use @wix/interact/react with the <Interaction> component.
Static / pre-rendered HTML (agent-generated .html, SSG export, Astro/Eleventy/Hugo output) → use @wix/interact/web with <interact-element> and follow the canonical CSS policy in references/integration-recipes.md.
Plain HTML, no bundler (hand-edited static .html, CDN runtime) → same as (2), using the CDN recipe for runtime wiring.
Bundled vanilla JS / other framework (Vite/Webpack but no React, or Vue/Svelte/Angular) → use @wix/interact/web (Web Components are framework-agnostic) or the base @wix/interact vanilla API. Prefer /web unless the user wants to control binding manually.
If you can't tell, ask the user which framework the page uses. The full
copy-paste setup for each entry point — including SSR, cleanup, and a verification
snippet — is in references/integration-recipes.md. Read it now for the entry
point you chose.
Prefer two phases — generation/build (all CSS possible) and runtime
(trigger wiring plus any runtime-dependent CSS):
// Generation/build script (Node, SSG, agent scratch)
import { Interact, generate } from '@wix/interact/web'; // or /react, or '@wix/interact'
import { FadeIn } from '@wix/motion-presets';
Interact.registerEffects({ FadeIn }); // BEFORE generate() — see invariants
const css = generate(config, true); // true=web, false=react/vanilla
// Deliver css according to the canonical policy in integration-recipes.md
For CSS delivery and runtime-only configs, follow the canonical policy in
references/integration-recipes.md.
If the config carries a $-prefixed plugin field (e.g. $splitText for text effects),
each phase gains one line — plugins: { … } in generate()'s options bag
above, Interact.use(…) before create() below. Both, or neither; see
references/plugins.md.
(For CDN/quick-start, import * as presets + registerEffects(presets) is fine at
generation time — selective imports just keep bundled apps lean. See references/presets.md.)
Mark up target elements with a key that matches the config:
// for vanilla - add the following
import { add } from '@wix/interact';
const el = document.querySelector('[data-interact-key="hero"]');
add(el);
Step 3 — Add / edit interactions
Before designing the config, draw on the example library for inspiration and reference patterns:
Read examples/index.md (this file is the table of contents — it lists every demo with its summary and tags).
Based on the user's request, identify 2–4 demos whose trigger type, layout, motion properties, or overall feel best match what's being built. Match on tags such as viewProgress, pointerMove, sticky, stagger, 3d, or clip-path.
Read those demo files from examples/examples/<category>/<name>.md. Use the index rather than guessing paths; the categories are gallery, carousel, image-background, text-animations, text-image, and ui-components.
Treat each demo as a cohesive unit — the interact config, HTML structure, and CSS layout are designed to work together. Adapt all three parts to the user's context rather than lifting any single piece in isolation.
This step is especially useful for: picking the right trigger/effect combination, handling complex layered compositions, and producing configs that feel polished rather than generic.
This is where most work happens. An InteractConfig is:
Choose the effect: prefer a namedEffect preset (browse references/presets.md); fall back to inline keyframeEffect for custom keyframes, or customEffect for non-CSS (SVG/canvas/text).
Set the playback field the trigger needs: triggerType for time effects on hover/click/viewEnter; stateAction for CSS-state (transition) effects; rangeStart/rangeEnd for viewProgress. Never set both triggerType and stateAction on one effect.
Bind it: give the target element the matching key in the markup. If the thing
you're animating is a stack of layers that should move together (hero
background + overlay + content, card image + text), key the one container
that wraps them and put a single effect on it — don't repeat the effect on each
layer (invariant 11).
Text per char/word/line: add $splitText on the interaction rather than
hand-rolling spans, then stagger the generated .split-c / .split-w /
.split-l / .split-s spans with selector on the effect inside a sequence.
Read references/plugins.md for the wiring, and note that on character-sized
targets a keyframeEffect usually beats a preset.
To edit an existing config: read the current config first, find the
interaction/effect by its key/effectId, and change only what's asked.
Preserve the rest (other interactions, ids, markup keys). After editing, re-run
validation (Step 4) — a changed namedEffect.type or a new
viewProgress effect can silently break if you skip it. If the effect catalog or
trigger semantics are involved, open references/presets.md / references/triggers.md.
For multi-target staggering (cards, lists, nav items), use sequences, not
manual per-item delays — see references/triggers.md and the sequences section of
references/config-schema.md.
Step 4 — Validate the config
No InteractConfig reaches generate() / Interact.create() unvalidated, and
no @wix/interact-validate reference ships in the code you deliver — on any
entry point, CDN included. How you run validation depends on whether you can
construct the config statically:
Static config (you authored a literal you can read in full): validate before
emit in a scratch script — never add validator imports to user files. See
references/validate.md for per-environment run mechanics. Validate (and
serialize) before calling generate()/create(), never after: both rewrite
the config in place, leaving it invalid — see config-schema.md.
Dynamic config (built at runtime from data/props/fetch/loops — you cannot
construct it by reading): temporarily inject assertValidInteractConfig(config)
immediately before generate()/create(), run so that code path executes, fix
every severity: 'error', then remove the call, import, any esm.sh import,
and any temp devDep. Prefer a dev-only validation script when the config builder
module is importable in isolation (no removal step). Full loop in
references/validate.md. For static site output, follow the canonical CSS
generation policy in references/integration-recipes.md.
Permanent guard (opt-in, separate): leaving assertValidInteractConfig in
shipped code as a devDependency CI gate is only when scaffolding a new project or
the user explicitly asks — do not conflate with the temporary injection above.
Fix every issue with severity: 'error' before proceeding; prefer fixing warnings
too. valid: false blocks emit.
Before declaring done, grep the files you're shipping:
grep -REn 'interact-validate|validateInteractConfig|assertValidInteractConfig|InteractValidationError' <shipped files>
# expect: no matches (unless the user asked for a permanent CI guard)
Then run the semantic checklist below.
Trigger → use-case quick reference
Trigger
Use for
Effect type & key field
viewEnter
Entrance animations when an element scrolls into view
Time effect; triggerType (default 'once')
viewProgress
Scroll-driven (parallax, reveal, scrub tied to scroll position)
Time effect (triggerType) or State effect (stateAction)
pointerMove
Cursor-following / tilt / parallax-on-mouse
Scrub effect; params.hitArea, params.axis
animationEnd
Chain one effect after another finishes
params.effectId of the preceding effect
Per-trigger deep rules and gotchas → references/triggers.md. Effect catalog (which preset for which look) → references/presets.md. Full
field-by-field schema for every config object → references/config-schema.md.
Critical invariants — get these wrong and output silently breaks
These are the failure modes that don't throw — the page just renders wrong or the
animation no-ops. Apply them every time, even if you don't open a reference file.
registerEffects() runs BEFORE generate() and Interact.create(). An
unregistered namedEffect.type doesn't error — it logs a console warning and
the animation never runs. Register the presets you use up front — prefer a
selective import { FadeIn, … } (tree-shakeable) over import * as presets in
bundled apps.
generate(config, useFirstChild) parity (or generate(config, { useFirstChild })) — pass true for the web
(<interact-element>) entry point, false for vanilla and React.
Backwards = the FOUC-prevention selectors target the wrong node and break.
FOUC prevention. Follow the canonical CSS generation policy in
references/integration-recipes.md. For the generated initial-rule behavior
and trigger-specific exceptions, see “CSS generation & FOUC” in
references/config-schema.md. Same-element viewEnter + once entrances get
author-important neutral initial rules from generate(). Always set
fill: 'backwards' on viewEnter + once animation effects (or 'both'
when the final keyframe must persist) so delayed entrances hold their first
keyframe after the entrance marker is set.
Vanilla binding. You must then call the standaloneadd(element, 'key') for
each element once it exists in the DOM. For clean up call the remove('key') function.
add/remove are functions imported from the package.
viewEnter with same source & target → only triggerType: 'once'. For
repeat/alternate/state, the animation can move the element out of/into the
viewport and re-trigger forever. Use separate source and target elements for
those.
Hit-area shift. On hover or pointerMove, if the effect changes the
element's size/position (scale, translate), the hovered hit-area shifts and
flickers. Keep the trigger on the stable parent and animate a child by
putting selector (or different key) on the effect — selector on the effect
sets the target; selector on the interaction sets the trigger's
source instead (the opposite of what you want).
viewProgress needs overflow: clip, not hidden.overflow: hidden on
any ancestor between the element and the scroll container creates a scroll
context that kills ViewTimeline. Replace every overflow: hidden with
overflow: clip (Tailwind: overflow-clip).
Never invent or guess. Use only real preset names (references/presets.md).
If you don't know a preset's option name/type, omit it and rely on defaults
— guessing produces silently-wrong output. Never emit DVD (exists in types but
isn't registered) or any Bg*/ImageParallax preset (experimental, not
production-ready). For "background parallax", use the public ParallaxScroll
on the image element with viewProgress.
Scroll presets carry a range. Every *Scroll preset needs
range: 'in' | 'out' | 'continuous' in its namedEffect
(prefer 'continuous') — exceptParallaxScroll, which takes parallaxFactor instead.
Lists: one keyed wrapper, fan out by selector or listContainer — never
duplicate keys. Keys are unique (one controller per key), so never put the
same key on N repeated elements — they'd clobber and only the last binds.
Instead key an ancestor wrapper and choose by who triggers: use
selector on the effect when one trigger staggers/animates many targets
(a viewEnter sequence over cards); use listContainer on the
interaction when each item needs its own trigger (per-card
hover/pointerMove, one tracker each). Either way the selector/
listContainer must match a descendant of the keyed element, not the keyed
element itself.
Layers that move as one → one keyed container, not the same effect on each
layer. When an element is composed of stacked layers meant to animate
together — a hero of background image + gradient overlay + content block, a
card of image + heading + text + button — put the trigger and one effect on
the wrapper that holds them and key that wrapper. Copying the same
FadeIn/SlideIn onto each layer is the common wrong turn: N layers become N
controllers that have to stay in sync (they visibly drift on slower devices), N
keys to wire, and N× the per-frame work for a motion the eye reads as a single
move. Collapse them onto the container. This is not the same as two cases
where separate targets are deliberate: scroll parallax, where layers move at
different rates on purpose (a ParallaxScroll per layer — keep those
separate), and hit-area-safe child targeting (invariant 6 — trigger on the
parent, animate one child). Litmus test: same trigger, same effect, same timing
across the layers ⇒ they belong on one keyed container.
Plugins come in halves — wire both or neither. A $-prefixed field
($splitText) needs Interact.use() before create() for the runtime half and
the plugin's SSR generator in generate()'s plugins option for the CSS half.
Half a wiring fails silently: with hideUntilReady but no runtime plugin the
container stays visibility: hidden forever, because nothing ever sets the ready
marker the generated CSS is waiting on. See references/plugins.md.
Verify your work (run before declaring done)
Animations are hard to confirm headlessly, so this static check is your reliable
proxy.
Automated config validation
validateInteractConfig(config) returns valid: true (no severity: 'error' issues). See references/validate.md.
Shipped files contain nointeract-validate, validateInteractConfig, assertValidInteractConfig, or InteractValidationError references (unless the user explicitly asked for a permanent CI guard).
Semantic & integration checklist
Items the validator cannot check — walk these after automated validation passes:
Every namedEffect.type is a real registered preset from references/presets.md (not DVD, not a Bg* preset, not invented).
Every *Scroll preset used with viewProgress has a range (except ParallaxScroll).
pointerMove effects have norangeStart/rangeEnd (those are viewProgress-only).
Every interaction key (and effect key) has a matching element in the markup (data-interact-key / interactKey).
Static/pre-rendered CSS follows the canonical policy in references/integration-recipes.md.
useFirstChild matches the entry point.
Child-target effects put selector/key on the effect, not the interaction. Groups of items use one keyed wrapper + a descendant match (no duplicate keys): selector on the effect for a one-trigger stagger/sequence, listContainer on the interaction for per-item triggers.
Composite elements whose layers animate as one unit are keyed on a single container with one effect — the same effect is not copied onto each layer (distinct from intentional per-layer parallax, which uses different rates, or child-targeting to avoid hit-area shift).
Invariants 5–7, 10, 11, and 12 hold for the relevant triggers (separate source/target, child targets, overflow: clip, unique keys, layers collapsed to one container, plugins registered and paired).
When using plugins: both halves wired (Interact.use() before create(), SSR generator in generate()); $-prefixed fields only; split targets carry fill: 'backwards' and a .split-* selector matching the classes the chosen type actually produces.
If a dev server is available, load the page and confirm the animation runs and the
browser console is free of "not found in registry" warnings.
Reference files
Read the one(s) relevant to the task — they are self-contained and source-accurate:
examples/index.md — table of contents for the curated demo library, with summaries, tags, and exact file links across all example categories.
references/config-schema.md — every config object field-by-field: InteractConfig, Interaction, all three effect variants, sequences, conditions, element resolution (source vs target), FOUC, and the full Interact static API.
references/triggers.md — per-trigger deep rules and gotchas: viewEnter, viewProgress, hover/click (+ triggerType/stateAction tables), pointerMove, animationEnd, accessibility variants, and sequences/stagger.
references/presets.md — the full preset catalog by category with parameters, defaults, accessibility risk tiers + reduced-motion fallbacks, and an "atmosphere → preset" selection guide.
references/integration-recipes.md — complete copy-paste setup per entry point (web / React / vanilla / CDN), with SSR, lifecycle/cleanup, and verification.
references/plugins.md — Interact's $-field plugin bridge (Interact.use, SSR style generators) with @wix/splittext as the worked example for per-char/word/line text animation.
references/validate.md — how to run @wix/interact-validate (static scratch script vs temporary injection for dynamic configs), options, limitations, and what the validator does not check.
references/motion-engine.md — thin escape-hatch reference for calling @wix/motion directly (programmatic getWebAnimation/getScrubScene/getSequence), easings, and engine gotchas. Only when the declarative config can't express what's needed.
1---2name: interactor3description: Use when user asks to add/edit motion without specific library, or with @wix/interact - hover, click, view/scroll triggered, scroll-driven, and pointer-driven animations for web. Wire animations to user interactions; install or set up @wix/interact; or edit an existing interact config. Do NOT use for other animation libraries.4---56# Interactor — build interactions with @wix/interact78This skill installs, wires up, and configures motion interactions so you can9add or edit interactions on any webpage or web app. It is **interact-first**: you10describe _what should animate and when_ as a declarative JSON config, and the11library does the DOM wiring. You almost never call the motion engine directly.1213## Mental model — packages and one config1415| Package | Role | You touch it… |16| :----------------------- | :--------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- |17| `@wix/interact` | Declarative layer. Binds **triggers → effects** via an `InteractConfig`. Ships vanilla / React / Web-Component entry points. | Always. This is the API. |18| `@wix/motion-presets` | Ready-made named effects (entrance, scroll, ongoing, mouse). Referenced as `namedEffect: { type: 'FadeIn' }`. | When you want a prebuilt effect (the common case). |19| `@wix/motion` | The engine (WAAPI, CSS, ViewTimeline, fastdom). Bundled inside interact. | Rarely — only for programmatic/escape-hatch animation. See `references/motion-engine.md`. |20| `@wix/interact-validate` | Static validator for `InteractConfig` shape (schema + referential checks). No DOM. | Agent-side validation always; optional dev/CI guard in user projects. See `references/validate.md`. |21| `@wix/splittext` | Splits text into per-char/word/line spans. Ships an Interact adapter at `@wix/splittext/plugin`. | When animating text per character, word, or line. See `references/plugins.md`. |2223The whole job is: **pick a trigger, pick an effect, bind it to an element with a24key.** Everything else is detail.2526```27┌── trigger (when) ──┐ ┌── effect (what) ────────────────┐28│ viewEnter, hover, │ ───► │ namedEffect: { type: 'FadeIn' } │ ──► applied to29│ click, viewProgress│ │ duration, easing, triggerType │ element with30│ pointerMove, … │ │ (or keyframeEffect/customEffect)│ matching key31└────────────────────┘ └─────────────────────────────────┘32```3334## Workflow3536Follow four steps in order: **Install → Integrate → Add/Edit interactions → Validate.** If37the project already uses interact (a config and the package exist), skip to38_Add/Edit_. Read the linked reference files as you reach each step — they hold the39full schema, the preset catalog, and per-trigger rules. Don't try to hold it all40in your head; the references are the source of truth.4142For static or pre-rendered output (agent-authored HTML, SSG, static export),43follow the canonical CSS generation policy in44`references/integration-recipes.md`.4546---4748### Step 1 — Install4950Both packages, one command. `@wix/motion` comes transitively inside51`@wix/interact` — **do not install it separately** on this path.5253```bash54npm install @wix/interact @wix/motion-presets55# (yarn add / pnpm add work too — match the project's package manager)56npm install @wix/splittext # only for per-char/word/line text animation; not transitive57npm install -D @wix/interact-validate # optional — permanent dev/CI config guard only58```5960A no-build / plain-HTML site can skip npm and import Interact from a CDN for61runtime wiring — see the CDN recipe in `references/integration-recipes.md`.62CDN pages skip the validate package install; the agent validates configs without63shipping the validator.6465---6667### Step 2 — Integrate6869**First, detect the stack and pick the entry point** (this determines every70import and a couple of flags). Decision procedure:71721. **React / Next / any JSX project** (a `package.json` with `react`, `.jsx`/`.tsx` files) → use `@wix/interact/react` with the `<Interaction>` component.732. **Static / pre-rendered HTML** (agent-generated `.html`, SSG export, Astro/Eleventy/Hugo output) → use `@wix/interact/web` with `<interact-element>` and follow the canonical CSS policy in `references/integration-recipes.md`.743. **Plain HTML, no bundler** (hand-edited static `.html`, CDN runtime) → same as (2), using the CDN recipe for runtime wiring.754. **Bundled vanilla JS / other framework** (Vite/Webpack but no React, or Vue/Svelte/Angular) → use `@wix/interact/web` (Web Components are framework-agnostic) **or** the base `@wix/interact` vanilla API. Prefer `/web` unless the user wants to control binding manually.7677If you can't tell, ask the user which framework the page uses. The full78copy-paste setup for each entry point — including SSR, cleanup, and a verification79snippet — is in **`references/integration-recipes.md`**. Read it now for the entry80point you chose.8182Prefer two phases — **generation/build** (all CSS possible) and **runtime**83(trigger wiring plus any runtime-dependent CSS):8485```ts86// Generation/build script (Node, SSG, agent scratch)87import { Interact, generate } from '@wix/interact/web'; // or /react, or '@wix/interact'88import { FadeIn } from '@wix/motion-presets';8990Interact.registerEffects({ FadeIn }); // BEFORE generate() — see invariants91const css = generate(config, true); // true=web, false=react/vanilla92// Deliver css according to the canonical policy in integration-recipes.md93```9495```ts96// Runtime (browser bundle / CDN module)97import { Interact } from '@wix/interact/web';9899const instance = Interact.create(config); // wire triggers100```101102For CSS delivery and runtime-only configs, follow the canonical policy in103`references/integration-recipes.md`.104105If the config carries a `$`-prefixed plugin field (e.g. `$splitText` for text effects),106each phase gains one line — `plugins: { … }` in `generate()`'s options bag107above, `Interact.use(…)` before `create()` below. Both, or neither; see108`references/plugins.md`.109110(For CDN/quick-start, `import * as presets` + `registerEffects(presets)` is fine at111generation time — selective imports just keep bundled apps lean. See `references/presets.md`.)112113Mark up target elements with a **key** that matches the config:114115```html116<!-- web -->117<interact-element data-interact-key="hero"><section>…</section></interact-element>118<!-- react -->119<Interaction tagName="section" interactKey="hero">…</Interaction>120<!-- vanilla -->121<section data-interact-key="hero">…</section>122```123124```js125// for vanilla - add the following126import { add } from '@wix/interact';127128const el = document.querySelector('[data-interact-key="hero"]');129add(el);130```131132---133134### Step 3 — Add / edit interactions135136**Before designing the config, draw on the example library for inspiration and reference patterns:**1371381. Read `examples/index.md` (this file is the table of contents — it lists every demo with its summary and tags).1392. Based on the user's request, identify 2–4 demos whose trigger type, layout, motion properties, or overall feel best match what's being built. Match on tags such as `viewProgress`, `pointerMove`, `sticky`, `stagger`, `3d`, or `clip-path`.1403. Read those demo files from `examples/examples/<category>/<name>.md`. Use the index rather than guessing paths; the categories are `gallery`, `carousel`, `image-background`, `text-animations`, `text-image`, and `ui-components`.1414. Treat each demo as a cohesive unit — the interact config, HTML structure, and CSS layout are designed to work together. Adapt all three parts to the user's context rather than lifting any single piece in isolation.142143This step is especially useful for: picking the right trigger/effect combination, handling complex layered compositions, and producing configs that feel polished rather than generic.144145---146147This is where most work happens. An `InteractConfig` is:148149```ts150{151 interactions: [ // REQUIRED — each binds one source+trigger to effect(s)152 { key, trigger, params?, effects?, sequences?, conditions?, selector?, listContainer?, listItemSelector? }153 ],154 effects?: { [effectId]: Effect }, // reusable effects, referenced by effectId155 sequences?: { [sequenceId]: SequenceConfig },156 conditions?:{ [conditionId]: Condition }, // media/selector gates157}158```159160To **add** an interaction:1611621. Choose the **trigger** (see decision table below).1632. Choose the **effect**: prefer a `namedEffect` preset (browse `references/presets.md`); fall back to inline `keyframeEffect` for custom keyframes, or `customEffect` for non-CSS (SVG/canvas/text).1643. Set the **playback field** the trigger needs: `triggerType` for time effects on hover/click/viewEnter; `stateAction` for CSS-state (transition) effects; `rangeStart`/`rangeEnd` for `viewProgress`. Never set both `triggerType` and `stateAction` on one effect.1654. Bind it: give the target element the matching `key` in the markup. If the thing166 you're animating is a stack of layers that should move together (hero167 background + overlay + content, card image + text), key the **one container**168 that wraps them and put a single effect on it — don't repeat the effect on each169 layer (invariant 11).1705. **Text per char/word/line:** add `$splitText` on the interaction rather than171 hand-rolling spans, then stagger the generated `.split-c` / `.split-w` /172 `.split-l` / `.split-s` spans with `selector` on the **effect** inside a sequence.173 Read `references/plugins.md` for the wiring, and note that on character-sized174 targets a `keyframeEffect` usually beats a preset.175176To **edit** an existing config: read the current config first, find the177interaction/effect by its `key`/`effectId`, and change _only_ what's asked.178Preserve the rest (other interactions, ids, markup keys). After editing, re-run179validation (Step 4) — a changed `namedEffect.type` or a new180`viewProgress` effect can silently break if you skip it. If the effect catalog or181trigger semantics are involved, open `references/presets.md` / `references/triggers.md`.182183For multi-target staggering (cards, lists, nav items), use **sequences**, not184manual per-item delays — see `references/triggers.md` and the sequences section of185`references/config-schema.md`.186187---188189### Step 4 — Validate the config190191No `InteractConfig` reaches `generate()` / `Interact.create()` unvalidated, and192**no `@wix/interact-validate` reference ships in the code you deliver** — on any193entry point, CDN included. How you run validation depends on whether you can194construct the config statically:195196- **Static config** (you authored a literal you can read in full): validate **before197 emit** in a scratch script — never add validator imports to user files. See198 `references/validate.md` for per-environment run mechanics. Validate (and199 serialize) **before** calling `generate()`/`create()`, never after: both rewrite200 the config in place, leaving it invalid — see `config-schema.md`.201- **Dynamic config** (built at runtime from data/props/fetch/loops — you cannot202 construct it by reading): temporarily inject `assertValidInteractConfig(config)`203 immediately before `generate()`/`create()`, run so that code path executes, fix204 every `severity: 'error'`, then **remove** the call, import, any `esm.sh` import,205 and any temp devDep. Prefer a dev-only validation script when the config builder206 module is importable in isolation (no removal step). Full loop in207 `references/validate.md`. For static site output, follow the canonical CSS208 generation policy in `references/integration-recipes.md`.209- **Permanent guard (opt-in, separate):** leaving `assertValidInteractConfig` in210 shipped code as a devDependency CI gate is only when scaffolding a new project or211 the user explicitly asks — do not conflate with the temporary injection above.212213Fix every issue with `severity: 'error'` before proceeding; prefer fixing warnings214too. `valid: false` blocks emit.215216**Before declaring done**, grep the files you're shipping:217218```bash219grep -REn 'interact-validate|validateInteractConfig|assertValidInteractConfig|InteractValidationError' <shipped files>220# expect: no matches (unless the user asked for a permanent CI guard)221```222223Then run the semantic checklist below.224225---226227## Trigger → use-case quick reference228229| Trigger | Use for | Effect type & key field |230| :------------------- | :-------------------------------------------------------------- | :-------------------------------------------------------------- |231| `viewEnter` | Entrance animations when an element scrolls into view | Time effect; `triggerType` (default `'once'`) |232| `viewProgress` | Scroll-driven (parallax, reveal, scrub tied to scroll position) | Scrub effect; `rangeStart`/`rangeEnd` |233| `hover` / `interest` | Hover effects (`interest` = hover+focus, accessible) | Time effect (`triggerType`) **or** State effect (`stateAction`) |234| `click` / `activate` | Click toggles (`activate` = click+keyboard, accessible) | Time effect (`triggerType`) **or** State effect (`stateAction`) |235| `pointerMove` | Cursor-following / tilt / parallax-on-mouse | Scrub effect; `params.hitArea`, `params.axis` |236| `animationEnd` | Chain one effect after another finishes | `params.effectId` of the preceding effect |237238Per-trigger deep rules and gotchas → **`references/triggers.md`**. Effect catalog (which preset for which look) → **`references/presets.md`**. Full239field-by-field schema for every config object → **`references/config-schema.md`**.240241---242243## Critical invariants — get these wrong and output silently breaks244245These are the failure modes that don't throw — the page just renders wrong or the246animation no-ops. Apply them every time, even if you don't open a reference file.2472481. **`registerEffects()` runs BEFORE `generate()` and `Interact.create()`.** An249 unregistered `namedEffect.type` doesn't error — it logs a console warning and250 the animation never runs. Register the presets you use up front — prefer a251 selective `import { FadeIn, … }` (tree-shakeable) over `import * as presets` in252 bundled apps.2532542. **`generate(config, useFirstChild)` parity** (or `generate(config, { useFirstChild })`) — pass `true` for the **web**255 (`<interact-element>`) entry point, `false` for **vanilla** and **React**.256 Backwards = the FOUC-prevention selectors target the wrong node and break.2572583. **FOUC prevention.** Follow the canonical CSS generation policy in259 `references/integration-recipes.md`. For the generated initial-rule behavior260 and trigger-specific exceptions, see “CSS generation & FOUC” in261 `references/config-schema.md`. Same-element `viewEnter` + `once` entrances get262 author-important neutral initial rules from `generate()`. Always set263 `fill: 'backwards'` on `viewEnter` + `once` animation effects (or `'both'`264 when the final keyframe must persist) so delayed entrances hold their first265 keyframe after the entrance marker is set.2662674. **Vanilla binding.** You must then call the **standalone** `add(element, 'key')` for268 each element once it exists in the DOM. For clean up call the `remove('key')` function.269 `add`/`remove` are functions imported from the package.2702715. **`viewEnter` with same source & target → only `triggerType: 'once'`.** For272 `repeat`/`alternate`/`state`, the animation can move the element out of/into the273 viewport and re-trigger forever. Use **separate** source and target elements for274 those.2752766. **Hit-area shift.** On `hover` or `pointerMove`, if the effect changes the277 element's size/position (`scale`, `translate`), the hovered hit-area shifts and278 flickers. Keep the trigger on the stable parent and animate a **child** by279 putting `selector` (or different `key`) on the **effect** — `selector` on the _effect_280 sets the **target**; `selector` on the _interaction_ sets the trigger's281 **source** instead (the opposite of what you want).2822837. **`viewProgress` needs `overflow: clip`, not `hidden`.** `overflow: hidden` on284 any ancestor between the element and the scroll container creates a scroll285 context that kills ViewTimeline. Replace every `overflow: hidden` with286 `overflow: clip` (Tailwind: `overflow-clip`).2872888. **Never invent or guess.** Use only real preset names (`references/presets.md`).289 If you don't know a preset's option name/type, **omit it** and rely on defaults290 — guessing produces silently-wrong output. Never emit `DVD` (exists in types but291 isn't registered) or any `Bg*`/`ImageParallax` preset (experimental, not292 production-ready). For "background parallax", use the public **`ParallaxScroll`**293 on the image element with `viewProgress`.2942959. **Scroll presets carry a `range`.** Every `*Scroll` preset needs296 `range: 'in' | 'out' | 'continuous'` in its `namedEffect`297 (prefer `'continuous'`) — **except** `ParallaxScroll`, which takes `parallaxFactor` instead.29829910. **Lists: one keyed wrapper, fan out by `selector` or `listContainer` — never300 duplicate keys.** Keys are unique (one controller per key), so never put the301 same key on N repeated elements — they'd clobber and only the last binds.302 Instead key an **ancestor wrapper** and choose by _who triggers_: use303 **`selector`** on the **effect** when one trigger staggers/animates many targets304 (a `viewEnter` sequence over cards); use **`listContainer`** on the305 **interaction** when each item needs its **own** trigger (per-card306 `hover`/`pointerMove`, one tracker each). Either way the `selector`/307 `listContainer` must match a **descendant** of the keyed element, not the keyed308 element itself.30931011. **Layers that move as one → one keyed container, not the same effect on each311 layer.** When an element is composed of stacked layers meant to animate312 **together** — a hero of background image + gradient overlay + content block, a313 card of image + heading + text + button — put the trigger and **one** effect on314 the wrapper that holds them and key that wrapper. Copying the same315 `FadeIn`/`SlideIn` onto each layer is the common wrong turn: N layers become N316 controllers that have to stay in sync (they visibly drift on slower devices), N317 keys to wire, and N× the per-frame work for a motion the eye reads as a single318 move. Collapse them onto the container. This is **not** the same as two cases319 where separate targets are deliberate: scroll **parallax**, where layers move at320 _different_ rates on purpose (a `ParallaxScroll` per layer — keep those321 separate), and **hit-area-safe child targeting** (invariant 6 — trigger on the322 parent, animate one child). Litmus test: same trigger, same effect, same timing323 across the layers ⇒ they belong on one keyed container.32432512. **Plugins come in halves — wire both or neither.** A `$`-prefixed field326 (`$splitText`) needs `Interact.use()` before `create()` for the runtime half and327 the plugin's SSR generator in `generate()`'s `plugins` option for the CSS half.328 Half a wiring fails silently: with `hideUntilReady` but no runtime plugin the329 container stays `visibility: hidden` forever, because nothing ever sets the ready330 marker the generated CSS is waiting on. See `references/plugins.md`.331332## Verify your work (run before declaring done)333334Animations are hard to confirm headlessly, so this static check is your reliable335proxy.336337### Automated config validation338339- [ ] `validateInteractConfig(config)` returns `valid: true` (no `severity: 'error'` issues). See `references/validate.md`.340- [ ] Shipped files contain **no** `interact-validate`, `validateInteractConfig`, `assertValidInteractConfig`, or `InteractValidationError` references (unless the user explicitly asked for a permanent CI guard).341342### Semantic & integration checklist343344Items the validator cannot check — walk these after automated validation passes:345346- [ ] Every `namedEffect.type` is a **real registered preset** from `references/presets.md` (not `DVD`, not a `Bg*` preset, not invented).347- [ ] Every `*Scroll` preset used with `viewProgress` has a `range` (except `ParallaxScroll`).348- [ ] `pointerMove` effects have **no** `rangeStart`/`rangeEnd` (those are `viewProgress`-only).349- [ ] Every interaction `key` (and effect `key`) has a **matching element** in the markup (`data-interact-key` / `interactKey`).350- [ ] Static/pre-rendered CSS follows the canonical policy in `references/integration-recipes.md`.351- [ ] `useFirstChild` matches the entry point.352- [ ] Child-target effects put `selector`/`key` on the **effect**, not the interaction. Groups of items use one keyed wrapper + a **descendant** match (no duplicate keys): `selector` on the effect for a one-trigger stagger/sequence, `listContainer` on the interaction for per-item triggers.353- [ ] Composite elements whose layers animate as one unit are keyed on a **single container** with one effect — the same effect is not copied onto each layer (distinct from intentional per-layer parallax, which uses different rates, or child-targeting to avoid hit-area shift).354- [ ] Invariants 5–7, 10, 11, and 12 hold for the relevant triggers (separate source/target, child targets, `overflow: clip`, unique keys, layers collapsed to one container, plugins registered and paired).355- [ ] When using plugins: both halves wired (`Interact.use()` before `create()`, SSR generator in `generate()`); `$`-prefixed fields only; split targets carry `fill: 'backwards'` and a `.split-*` selector matching the classes the chosen `type` actually produces.356357If a dev server is available, load the page and confirm the animation runs and the358browser console is free of "not found in registry" warnings.359360## Reference files361362Read the one(s) relevant to the task — they are self-contained and source-accurate:363364- **`examples/index.md`** — table of contents for the curated demo library, with summaries, tags, and exact file links across all example categories.365- **`references/config-schema.md`** — every config object field-by-field: `InteractConfig`, `Interaction`, all three effect variants, sequences, conditions, element resolution (source vs target), FOUC, and the full `Interact` static API.366- **`references/triggers.md`** — per-trigger deep rules and gotchas: `viewEnter`, `viewProgress`, `hover`/`click` (+ `triggerType`/`stateAction` tables), `pointerMove`, `animationEnd`, accessibility variants, and sequences/stagger.367- **`references/presets.md`** — the full preset catalog by category with parameters, defaults, accessibility risk tiers + reduced-motion fallbacks, and an "atmosphere → preset" selection guide.368- **`references/integration-recipes.md`** — complete copy-paste setup per entry point (web / React / vanilla / CDN), with SSR, lifecycle/cleanup, and verification.369- **`references/plugins.md`** — Interact's `$`-field plugin bridge (`Interact.use`, SSR style generators) with `@wix/splittext` as the worked example for per-char/word/line text animation.370- **`references/validate.md`** — how to run `@wix/interact-validate` (static scratch script vs temporary injection for dynamic configs), options, limitations, and what the validator does not check.371- **`references/motion-engine.md`** — thin escape-hatch reference for calling `@wix/motion` directly (programmatic `getWebAnimation`/`getScrubScene`/`getSequence`), easings, and engine gotchas. Only when the declarative config can't express what's needed.
Run npx skillmds@latest add wix/interactor in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when user asks to add/edit motion without specific library, or with @wix/interact - hover, click, view/scroll triggered, scroll-driven, and pointer-driven animations for web. Wire animations to user interactions; install or set up @wix/interact; or edit an existing interact config. Do NOT use for other animation libraries. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
wix (@wix) published this skill. Their other Agent Skills are listed on their SkillMD profile.