npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wix/interact

v2.4.0

Published

Declarative, configuration-driven interaction library — web-native, AI-ready, and framework-agnostic.

Readme

@wix/interact

Declarative, configuration-driven interaction library — web-native, AI-ready, and framework-agnostic.

npm version bundle size license downloads

Why Interact?

  • Declarative — Define trigger-to-effect bindings in JSON; no imperative event wiring
  • Web-native — Built on CSS, WAAPI, ViewTimeline, and DOM APIs; supports DOM management via Custom Elements
  • Framework-agnostic — Web Components and vanilla JS integrations; React integration included
  • AI-ready — JSON configs are machine-readable and provide guardrails; LLMs can generate and agents can validate them
  • CSS generationgenerate(config) emits complete CSS for the whole config (@keyframes, view-timeline, transitions, FOUC rules)
  • Preset ecosystem — Plug in @wix/motion-presets for 75+ ready-made effects.
  • Accessible — Built-in activate (click + keyboard) and interest (hover + focus) trigger variants

Install

npm install @wix/interact

Use pre-made presets

npm install @wix/motion-presets

@wix/motion-presets is optional but recommended — it provides the namedEffect library used in most examples below.

Quick Start

Using Web Components (recommended)

Web Components — wrap the target element with <interact-element>:

import { Interact, generate, type InteractConfig } from '@wix/interact/web';
import * as presets from '@wix/motion-presets'; // required when using namedEffect

Interact.registerEffects(presets); // required when using namedEffect

const config: InteractConfig = {
  interactions: [
    {
      key: 'hero',
      trigger: 'viewEnter',
      effects: [{ effectId: 'hero-in' }],
    },
  ],
  effects: {
    'hero-in': {
      duration: 800,
      easing: 'ease-out',
      namedEffect: { type: 'FadeIn' }, // requires motion-presets
      triggerType: 'once',
    },
  },
};

// render styles  - e.g. for SSR
const interactCSS = generate(config, true);

// run on client - e.g. on pagereveal event
const instance = Interact.create(config);

In <head> add:

<style>
  ${interactCSS}
  /* Optional — keep the custom element from affecting layout */
  interact-element {
    display: contents;
  }
</style>

In the <body> add:

<interact-element data-interact-key="hero">
  <section class="hero">Hello, animated world!</section>
</interact-element>

Using React

A complete React example: register presets, generate CSS, mount the component, clean up on unmount.

import { useEffect } from 'react';
import type { InteractConfig } from '@wix/interact';
import { Interact, Interaction, generate } from '@wix/interact/react';
import * as presets from '@wix/motion-presets'; // optional

Interact.registerEffects(presets); // optional

const config: InteractConfig = {
  //...
};

export function App () {
  const interactCSS = generate(config, false);
  // rest of App logic ...

  useEffect(() => {
    const instance = Interact.create(config);
    return () => instance.destroy();
  }, []);

  return (
    // can go to <head>
    <style>{interactCSS}</style>
    // ...
    <Hero/>
    // ...
  )
}

// in components/Hero.jsx
export function Hero() {
  return (
    <Interaction tagName="section" interactKey="hero">
      Hello, animated world!
    </Interaction>
  );
}

Using Vanilla JS (you manage element lifecycle)

Vanilla JS — bind elements after they exist in the DOM:

import type { InteractConfig } from '@wix/interact';
import { Interact, add } from '@wix/interact';

const config: InteractConfig = {
  //...
};

const instance = Interact.create(config);

add(document.querySelector('#hero'), 'hero');

Entry Points

| Import | Use When | | --------------------- | ----------------------------------------------------------- | | @wix/interact | Vanilla JS — manual element binding via add()/remove(). | | @wix/interact/web | Web Components — <interact-element> custom element. | | @wix/interact/react | React — <Interaction> component with lifecycle. |

All three entry points export the same Interact class, generate() function, and types.

How It Works

Config ─┬─► Interact.create() ─► Trigger Observers ─► Effect Engine ─► Animation (via @wix/motion)
        └─► generate() ────────► CSS (@keyframes, view-timeline, animations, transitions) ─► <head>

generate(config) runs at build time or on the server to emit complete CSS for the entire config — maximizing offload of effect creation, binding, and running to the browser. Interact also generates native view-timeline CSS declarations, so browsers that support it can drive scroll animations entirely without JS.

The InteractConfig shape:

type InteractConfig = {
  interactions: Interaction[]; // trigger → effect bindings
  effects?: Record<string, Effect>; // reusable effect definitions
  sequences?: Record<string, SequenceConfig>; // staggered multi-effect timelines
  conditions?: Record<string, Condition>; // media / selector gates
};

Triggers

| Trigger | Fires On | Params | | -------------- | -------------------------------------------- | --------------------------------------- | | viewEnter | Element enters viewport | threshold?, inset? | | viewProgress | While element scrolls through viewport | (use rangeStart/rangeEnd on effect) | | hover | Pointer enters/leaves element | — | | click | Element is clicked | — | | activate | Click + keyboard (a11y variant of click) | — | | interest | Hover + focus (a11y variant of hover) | — | | pointerMove | While pointer moves over element or viewport | hitArea?, axis? | | animationEnd | Another specified effect is finished | effectId |

Effects

| Effect Type | Use For | | ------------------------------------- | -------------------------------------------------------------------------- | | keyframeEffect | Inline keyframes — self-contained, no preset needed. | | namedEffect | Registered presets from @wix/motion-presets (e.g. { type: 'FadeIn' }). | | customEffect | Programmatic (element, progress) => void callback. | | transition / transitionProperties | CSS state changes driven by stateAction (add/remove/toggle). |

Recipes

Each example is a complete InteractConfig — pass it to Interact.create(config).

Entrance animation

{
  interactions: [{
    key: 'hero',
    trigger: 'viewEnter',
    effects: [{ effectId: 'float-in' }],
  }],
  effects: {
    'float-in': {
      duration: 800,
      easing: 'ease-out',
      namedEffect: { type: 'FloatIn', direction: 'bottom' },
    },
  },
}

Click effect

{
  interactions: [{
    key: 'cta',
    trigger: 'click',
    effects: [{ effectId: 'pulse' }],
  }],
  effects: {
    'pulse': {
      duration: 300,
      keyframeEffect: {
        name: 'pulse',
        keyframes: [
          { transform: 'scale(1.08)', offset: 0.5 }
        ],
      },
      triggerType: 'repeat',
    },
  },
}

Scroll-driven animations

{
  interactions: [{
    key: 'card',
    trigger: 'viewProgress',
    effects: [{ effectId: 'parallax' }],
  }],
  effects: {
    'parallax': {
      keyframeEffect: {
        name: 'parallax',
        keyframes: [
          { transform: 'translateY(-120px)' },
          { transform: 'translateY(120px)' },
        ],
      },
      rangeStart: { name: 'cover', offset: { value: 0, unit: 'percentage' } },
      rangeEnd: { name: 'cover', offset: { value: 100, unit: 'percentage' } },
      fill: 'both',
      easing: 'linear',
    },
  },
}

Hover toggle

CSS transition

{
  interactions: [{
    key: 'card',
    trigger: 'hover',
    effects: [{ effectId: 'lift', selector: '.card-figure' }],
  }],
  effects: {
    'lift': {
      transition: {
        duration: 200,
        easing: 'ease-out',
        styleProperties: [
          { name: 'transform', value: 'scale(1.08)' },
          { name: 'box-shadow', value: '0 8px 16px rgb(0 0 0 / 0.15)' },
        ],
      },
    },
  },
}

CSS Animation

{
  interactions: [{
    key: 'card',
    trigger: 'hover',
    effects: [{ effectId: 'lift', selector: '.card-figure' }],
  }],
  effects: {
    'lift': {
      keyframeEffect: {
        name: 'lift',
        keyframes: [
          { transform: 'translateY(-80px)', boxShadow: '0 8px 16px rgb(0 0 0 / 0.15)' },
        ],
      },
      duration: 200,
      easing: 'ease-out',
    },
  },
}

Pointer-tracking

Keyframe effect

{
  interactions: [{
    key: 'card-wrapper',
    trigger: 'pointerMove',
    params: { hitArea: 'root', axis: 'x' },
    effects: [{ effectId: 'follow-x', key: 'card' }],
  }, {
    key: 'card-wrapper',
    trigger: 'pointerMove',
    params: { hitArea: 'root', axis: 'y' },
    effects: [{ effectId: 'follow-y', key: 'card' }],
  }],
  effects: {
    'follow-x': {
      keyframeEffect: {
        name: 'follow-x',
        keyframes: [
          { transform: 'rotateY(-45deg)' },
          { transform: 'rotateY(0px)' },
          { transform: 'rotateY(45deg)' },
        ],
      },
      easing: 'linear',
      centeredToTarget: true,
    },
    'follow-y': {
      keyframeEffect: {
        name: 'follow-y',
        keyframes: [
          { transform: 'rotateX(45deg)' },
          { transform: 'rotateX(0px)' },
          { transform: 'rotateX(-45deg)' },
        ],
      },
      easing: 'linear',
      composite: 'add',
      centeredToTarget: true,
    },
  },
}

Custom effect

{
  interactions: [{
    key: 'spotlight',
    trigger: 'pointerMove',
    params: { hitArea: 'root' },
    effects: [{ effectId: 'follow' }],
  }],
  effects: {
    'follow': {
      customEffect: (element: HTMLElement, progress: { x: number, y: number }) => {
        element.style.setProperty('--x', `${progress.x * 100}%`);
        element.style.setProperty('--y', `${progress.y * 100}%`);
      },
    },
  },
}

Common Pitfalls

  • overflow: hidden breaks viewProgress — Use overflow: clip on all ancestors between the source and the scroll container.
  • Same element as source and target with viewEnter — Must use triggerType: 'once'. Other types cause re-entry loops.
  • Hit-area shift on hover / pointerMove — Animating size/position of the hovered element shifts the hit area and causes jitter. Instead, animate a child via selector or a different key.
  • registerEffects() must run before Interact.create()/generate() when using namedEffect.
  • FOUC prevention — requires injecting the output of generate(config) into <head>.
  • generate(config, useFirstChild) — Pass true for <interact-element> (web), false for vanilla and React <Interaction>.
  • <interact-element> must wrap exactly one child — the library targets :first-child by default.

AI & Agent Support

Interact's JSON-config surface is the differentiator: configs are serializable, schema-typed, and validate-able (guardrails) — no imperative DOM logic for an LLM to hallucinate.

AI agents can discover @wix/interact documentation through:

Rules files ship with the package under rules/ — point your agent at them:

Generation constraints for agents producing configs:

  • Do not invent namedEffect types — use only registered presets.
  • Do not attach DOM event listeners manually — use triggers.
  • Do not use overflow: hidden on scroll-tracked ancestors — use overflow: clip.
  • Always pre-render CSS with generate(config) and inject into <head>.
  • Always call Interact.registerEffects(presets) before generate() and Interact.create() when using namedEffect.

Browser Support

  • Modern browsers with the Web Animations API (Baseline).
  • adoptedStyleSheets (used by transition / transitionProperties): Chrome 73+, Firefox 101+, Safari 16.4+, Edge 79+.
  • ViewTimeline: Chrome 115+; polyfilled via fizban elsewhere.

Related Packages

  • @wix/motion — low-level animation engine underneath Interact.
  • @wix/motion-presets — ready-made effect catalog (entrance, scroll, hover, pointer).
  • fizban — scroll-driven animation polyfill (bundled dependency).
  • kuliso — pointer-driven animation polyfill (bundled dependency).

Documentation

License

MIT