- name
- interactor
- description
- 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.
```
┌── trigger (when) ──┐ ┌── effect (what) ────────────────┐
│ viewEnter, hover, │ ───► │ namedEffect: { type: 'FadeIn' } │ ──► applied to
│ click, viewProgress│ │ duration, easing, triggerType │ element with
│ pointerMove, … │ │ (or keyframeEffect/customEffect)│ matching key
└────────────────────┘ └─────────────────────────────────┘
```
## Workflow
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.
```bash
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:
1. **React / Next / any JSX project** (a `package.json` with `react`, `.jsx`/`.tsx` files) → use `@wix/interact/react` with the `<Interaction>` component.
2. **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`.
3. **Plain HTML, no bundler** (hand-edited static `.html`, CDN runtime) → same as (2), using the CDN recipe for runtime wiring.
4. **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):
```ts
// 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
```
```ts
// Runtime (browser bundle / CDN module)
import { Interact } from '@wix/interact/web';
const instance = Interact.create(config); // wire triggers
```
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:
```html
<!-- web -->
<interact-element data-interact-key="hero"><section>…</section></interact-element>
<!-- react -->
<Interaction tagName="section" interactKey="hero">…</Interaction>
<!-- vanilla -->
<section data-interact-key="hero">…</section>
```
```js
// 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:**
1. Read `examples/index.md` (this file is the table of contents — it lists every demo with its summary and tags).
2. 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`.
3. 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`.
4. 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:
```ts
{
interactions: [ // REQUIRED — each binds one source+trigger to effect(s)
{ key, trigger, params?, effects?, sequences?, conditions?, selector?, listContainer?, listItemSelector? }
],
effects?: { [effectId]: Effect }, // reusable effects, referenced by effectId
sequences?: { [sequenceId]: SequenceConfig },
conditions?:{ [conditionId]: Condition }, // media/selector gates
}
```
To **add** an interaction:
1. Choose the **trigger** (see decision table below).
2. 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).
3. 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.
4. 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).
5. **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:
```bash
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) | Scrub effect; `rangeStart`/`rangeEnd` |
| `hover` / `interest` | Hover effects (`interest` = hover+focus, accessible) | Time effect (`triggerType`) **or** State effect (`stateAction`) |
| `click` / `activate` | Click toggles (`activate` = click+keyboard, accessible) | Time effect (`triggerType`) **or** State effect (`stateAction`) |
| `pointerMove` | Cursor-following / tilt / parallax-on-mouse | Scrub effect; `params.hitArea`, `params.axis` |
View on GitHub