Interact.create(config) once to initialize.create call to avoid unintended overrides; subsequent calls replace the previous config.import { Interact } from '@wix/interact';
import type { InteractConfig } from '@wix/interact';
const config: InteractConfig = {
// config-props
};
Interact.create(config);
<script type="module"> and import from the CDN.<script type="module">
import { Interact } from 'https://esm.sh/@wix/interact@1.86.0';
const config = {
// config-props
};
Interact.create(config);
</script>
namedEffect preset options) can produce console errors. If you do not know the expected type/structure for a param, omit it and rely on defaults rather than guessing.overflow: hidden or overflow: auto can break viewProgress animations. Prefer overflow: clip for clipping semantics while preserving normal ViewTimeline.transform: perspective(...) inside keyframes/presets. Reserve the static CSS perspective property for the specific case where multiple children of the same container must share the same viewpoint (perspective-origin).viewProgress (ViewTimeline): Creating a new stacking context on the target or any of its ancestors can prevent or freeze ViewTimeline sampling in some engines and setups. Avoid stacking‑context‑creating styles on the observed subtree (target and ancestors), including transform, filter, perspective, opacity < 1, mix-blend-mode, isolation: isolate, aggressive will-change, and contain: paint/layout/size. If needed for visuals, wrap the content and apply these styles to an inner child so the element that owns the timeline remains “flat”. Also avoid turning the scroll container into a stacking context; if you need clipping, prefer overflow: clip and avoid transform on the container. Typical symptoms are viewProgress not running, jumping 0→1, or never reaching anchors—remove or relocate the offending styles.This configuration declares what user/system triggers occur on which source element(s), and which visual effects should be applied to which target element(s). It is composed of three top-level sections: effects, conditions, and interactions.
interactions array. You SHOULD provide an effects registry when you want to reference reusable effects by id. conditions are OPTIONAL.EffectRef.effectId MUST exist in effects).key fields) refer to the element path string (e.g., the value used in data-interact-key) and MUST be stable for the lifetime of the configuration.<interact-element> and set data-interact-key to the element key; use that same key in your config (Interaction.key/Effect.key). The runtime binds triggers/effects via this attribute.EffectRef.'media' | 'container'
'media': The predicate MUST be a valid CSS media query expression without the outer @media keyword (e.g., '(min-width: 768px)').'container': The predicate SHOULD be a valid CSS container query condition string relative to the relevant container context.Interaction contains:
listContainer.'hover' | 'click': Pointer interactions.'viewEnter' | 'pageVisible' | 'viewProgress': Viewport visibility/progress triggers.'animationEnd': Fires when a specific effect completes on the source element.'pointerMove': Continuous pointer motion over an area.StateParams | PointerTriggerParams
StateParams.method: 'add' | 'remove' | 'toggle' | 'clear'PointerTriggerParams.type?: 'once' | 'repeat' | 'alternate' | 'state'TransitionEffect, use StateParams.method to control the state toggle invoked on interaction:
'toggle' (default): Hover — adds on enter and removes on leave. Click — toggles on each click.'add': Apply the state on the event; hover leave will NOT auto‑remove.'remove': Remove the state on the event.'clear': Clear/reset the effect’s state for the element (or list item when list context is used).listContainer/listItemSelector), the state is set on the matching item only.namedEffect/keyframeEffect), use PointerTriggerParams.type:
'alternate' (default): Hover — play on enter, reverse on leave. Click — alternate play/reverse on successive clicks.'repeat': Restart from progress 0 on each event; on hover leave the animation is canceled.'once': Play once and remove the listener (hover attaches only the enter listener; no leave).'state': Hover — play on enter if idle/paused, pause on leave if running. Click — toggle play/pause on successive clicks until finished.ViewEnterParams
type?: 'once' | 'repeat' | 'alternate'threshold?: number in [0,1] describing intersection thresholdinset?: string CSS-style inset for rootMargin/observer geometry'once': Play on first visibility and unobserve the element.'repeat': Play each time the element re‑enters visibility according to threshold/inset.'alternate': Triggers on re‑entries; if you need alternating direction, set it on the effect (e.g., alternate: true) rather than relying on the trigger.threshold: Passed to IntersectionObserver.threshold — typical values are 0.1–0.6 for entrances.inset: Applied as vertical rootMargin (top/bottom), e.g., '-100px' to trigger earlier/later; left/right remain 0.viewProgress, threshold and inset are ignored; progress is driven by ViewTimeline/scroll scenes. Control the range via ScrubEffect.rangeStart/rangeEnd and namedEffect.range.AnimationEndParams
effectId: string of the effect to wait for completioneffectId) on the source element finishes, useful for chaining sequences.PointerMoveParams
hitArea?: 'root' | 'self' (default 'self')'self': Track pointer within the source element’s bounds.'root': Track pointer anywhere in the viewport (document root).ScrubEffect mouse presets (namedEffect) or customEffect that consumes pointer progress; avoid keyframeEffect with pointerMove.<interact-element>data-interact-key to a stable key. Reference that same key from your config via Interaction.key (and optionally Effect.key). No observers/listeners or manual DOM querying are needed—the runtime binds triggers and effects by this attribute.<interact-element> and set data-interact-key to the target’s key (the value used in Effect.key or the referenced registry Effect’s key). This is required so the runtime can locate and apply effects to non-source targets.<interact-element data-interact-key="my-button">
<button id="my-button">Click me</button>
</interact-element>
import type { InteractConfig } from '@wix/interact';
const config: InteractConfig = {
interactions: [
{
key: 'my-button', // matches data-interact-key
trigger: 'hover',
effects: [
{
// key omitted -> targets the source element ('my-button')
// effect props go here (e.g., transition | keyframeEffect | namedEffect | customEffect)
},
],
},
],
};
For a different target element:
<interact-element data-interact-key="my-button">
<button id="my-button">Click me</button>
</interact-element>
<interact-element data-interact-key="my-badge">
<span id="my-badge">Badge</span>
</interact-element>
const config: InteractConfig = {
interactions: [
{
key: 'my-button',
trigger: 'click',
effects: [
{
key: 'my-badge', // target is different than source
// effect props (e.g., transition | keyframeEffect | namedEffect)
},
],
},
],
};
EffectBase):
Effect.key (if provided)
2) If Effect is an EffectRef: lookup registry by effectId and use that registry Effect’s key
3) Fallback to the Interaction.key (i.e., source acts as target)EffectRef this field is REQUIRED and MUST reference an entry in effects.composite default: 'replace'.fill default: 'none'.effects[] defines application order; later overlapping properties can override earlier ones if composite: 'replace'.composite: 'add' or composite: 'accumulate' on those effects so they compose rather than clobber each other.
'add' for additive properties (e.g., transforms, filters, opacity) so values blend.'accumulate' when iterations/repeats should build on previous cycles.'add'/'accumulate' behaves like 'replace'; avoid splitting control of that property across multiple effects—prefer a single effect to own any non-additive property.'replace' only when you intentionally want a later effect to override earlier ones; set it explicitly for clarity.composite (similar to CSS animation-composition / WAAPI composite):
'replace': fully replaces prior values for overlapping properties.'add': adds to the underlying value where the property supports additive composition (e.g., transforms, filters, opacity).'accumulate': values build up across iterations/repeats where supported.'add'/'accumulate' like 'replace'.fill (like CSS animation-fill-mode):
'none' (default): styles are applied only while the effect is actively running/in-range.'forwards': the end state is retained after completion (or last sampled scrub value).'backwards': the start state applies before the effect begins (or before rangeStart for scrub / during delay for time effects).'both': combines 'backwards' and 'forwards', keeping visuals stable before and after the active phase.fill: 'both' in general, and MUST use fill: 'both' for scroll‑driven animations (viewProgress) to preserve start/end states and avoid flicker when entering/leaving the active range or on rapid scroll.fill: 'both' for all effects; for scroll/scrub effects, this is REQUIRED.composite: 'add' or 'accumulate' on those effects so they combine without overriding.composite: 'replace' when you intentionally want to override a prior effect; set it explicitly.effects[] order is significant: the first listed effect is applied first; later effects may add to or override earlier ones depending on composite.Example (two effects combining on the same target):
effects: [
{
// Scroll-driven parallax; keeps start/end states, composes with other transforms
rangeStart: { name: 'entry', offset: { value: 0, type: 'percentage' } },
rangeEnd: { name: 'exit', offset: { value: 0, type: 'percentage' } },
namedEffect: { type: 'ParallaxScroll', range: 'continuous' },
fill: 'both',
composite: 'add',
},
{
// Mouse tilt; composes additively with the parallax transform
namedEffect: { type: 'Tilt3DMouse' },
fill: 'both',
composite: 'add',
},
];
Effect (exactly one MUST be provided via discriminated fields):
1) TimeEffect (discrete animation over time)
duration: number (REQUIRED)easing?: string (CSS/WAAPI easing)iterations?: number (>=1 or Infinity)alternate?: boolean (direction alternation)fill?: 'none' | 'forwards' | 'backwards' | 'both'composite?: 'replace' | 'add' | 'accumulate'reversed?: booleandelay?: number (ms)keyframeEffect: { name: string; keyframes: Keyframe[] }namedEffect: NamedEffect (from @wix/motion)customEffect: (element: Element, progress: any) => void2) ScrubEffect (animation driven by scroll/progress)
easing?: stringiterations?: number (NOT Infinity)alternate?: booleanfill?: 'none' | 'forwards' | 'backwards' | 'both'composite?: 'replace' | 'add' | 'accumulate'reversed?: booleanrangeStart: RangeOffsetrangeEnd: RangeOffsetcenteredToTarget?: booleantransitionDuration?: number (ms for smoothing on progress jumps)transitionDelay?: numbertransitionEasing?: ScrubTransitionEasingkeyframeEffect | namedEffect | customEffect (see above)pointerMove trigger, do NOT use keyframeEffect (pointer progress is two‑dimensional and cannot be mapped to linear keyframes). Use namedEffect mouse presets instead, or customEffect for custom‑made animations.namedEffect presets (e.g., *Scroll) used with a viewProgress trigger, include range: 'in' | 'out' | 'continuous' in the namedEffect options; prefer 'continuous' for simplicity.rangeStart/rangeEnd):
{ name: 'entry' | 'exit' | 'contain' | 'cover' | 'entry-crossing' | 'exit-crossing'; offset: LengthPercentage }LengthPercentage that shifts the anchor boundary.
{ value: number; type: 'percentage' | 'px' | 'em' | 'rem' | 'vh' | 'vw' | 'vmin' | 'vmax' }rangeStart: { name: 'entry', offset: { value: 20, type: 'percentage' } }rangeEnd: { name: 'exit', offset: { value: 0, type: 'percentage' } }3) TransitionEffect (CSS transition-style state toggles)
key?: string (target override; see TARGET CASCADE)effectId?: string (when used as a reference identity)transition?: { duration?: number; delay?: number; easing?: string; styleProperties: { name: string; value: string }[] }
transitionProperties?: Array<{ name: string; value: string; duration?: number; delay?: number; easing?: string }>
transition and transitionProperties are provided, the system SHOULD apply both with per-property entries taking precedence for overlapping properties.namedEffect, keyframeEffect, customEffect):@wix/motion that are GPU-friendly and tuned.
namedEffect: { type: '<PresetName>', /* optional preset options like direction (bottom|top|left|right), power ('soft'|'medium'|'hard'), etc. do not use those without having proper documentation of which options exist and of what types. */ }FadeIn, BounceIn, SlideIn, FlipIn, ArcInPulse, Spin, Wiggle, BounceParallaxScroll, FadeScroll, GrowScroll, RevealScroll, TiltScrollviewProgress trigger, the namedEffect options MUST include range: 'in' | 'out' | 'continuous'. Prefer range: 'continuous' for simplicity.pointerMove (mouse-effects), prefer namedEffect presets (e.g., TrackMouse, Tilt3DMouse, ScaleMouse, BlurMouse); avoid keyframeEffect with pointerMove since progress is two‑dimensional.TrackMouse, Tilt3DMouse, ScaleMouse, BlurMousekeyframeEffect: { name: string; keyframes: Keyframe[] } (keyframes use standard CSS/WAAPI properties).pointerMove (mouse-effects) because pointer progress is two‑dimensional; use customEffect for custom pointer‑driven animations.customEffect: (element: Element, progress: any) => voidEffect.key -> registry Effect.key (for EffectRef) -> Interaction.key.listContainer is present on the interaction, the selector resolution may be widened to include list items (optionally filtered by listItemSelector), and then further refined by any provided selector.conditions to provide responsive and accessible behavior:
'(prefers-reduced-motion: reduce)' and breakpoint queries, and attach them to interactions/effects to disable, simplify, or swap animations when appropriate.conditions or effect references so that users who prefer reduced motion get a gentler experience.For AI Agents: llms.txt