Animation

Effex uses CSS-based animations for enter/exit transitions. Animations are configured on control flow primitives — when, match, each — so they happen automatically when the reactive state changes.

Enter/Exit Animations

Add an animate option to any control flow primitive:

import { when } from "@effex/dom";

when(isOpen, {
  onTrue: () => Modal(),
  onFalse: () => $.span(),
  animate: {
    enterFrom: "opacity-0 scale-95",
    enterTo: "opacity-100 scale-100",
    exit: "fade-out",
  },
});

When isOpen becomes true, the entering element starts with the enterFrom classes, then transitions to enterTo. When it becomes false, the exit classes are applied and the element is removed after the animation completes.

Animation Options

Option Description
enter Classes applied during the entire enter transition
enterFrom Classes applied on the first frame, removed on the next
enterTo Classes applied after enterFrom is removed
exit Classes applied during the entire exit transition
exitTo Classes applied after exit, element removed when transition ends

This follows the same model as Vue and Alpine.js transitions — you define CSS classes, and Effex manages the timing.

List Animations

Animate items entering and leaving a list:

import { each, stagger } from "@effex/dom";

each(items, {
  key: (item) => item.id,
  render: (item) => ListItem(item),
  animate: {
    enter: "slide-in",
    exit: "slide-out",
    stagger: stagger(50),  // 50ms between items
  },
});

When items are added, each one’s enter animation starts 50ms after the previous one. When items are removed, the exit animation plays before the DOM node is removed.

Stagger Functions

Stagger functions control the timing between animated items in a list:

import {
  stagger,
  staggerFromCenter,
  staggerEased,
  delay,
  sequence,
  parallel,
} from "@effex/dom";
Function Description
stagger(delayMs) Fixed delay between items — item 0 starts immediately, item 1 at 50ms, item 2 at 100ms, etc.
staggerFromCenter(delayMs) Items animate outward from the center of the list
staggerEased(totalDurationMs, easingFn) Distribute items across a total duration using an easing curve
delay(delayMs) Same fixed delay for all items
sequence(...delays) Explicit delay for each item position
parallel() All items animate simultaneously (no stagger)

Example: Center-Out Stagger

each(menuItems, {
  key: (item) => item.id,
  render: (item) => MenuItem(item),
  animate: {
    enter: "scale-in",
    stagger: staggerFromCenter(30),
  },
});

Items in the middle of the list animate first, then the animation ripples outward to both ends.

CSS Setup

Effex applies classes but doesn’t include any CSS. Define your transitions in your stylesheet:

/* Fade in */
.fade-in {
  transition: opacity 150ms ease-in;
}

/* Slide in from below */
.slide-in {
  animation: slideIn 200ms ease-out;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Fade out */
.fade-out {
  transition: opacity 150ms ease-out;
  opacity: 0;
}

With Tailwind CSS, you can use utility classes directly:

animate: {
  enterFrom: "opacity-0 translate-y-2",
  enter: "transition-all duration-150",
  enterTo: "opacity-100 translate-y-0",
  exit: "transition-all duration-150",
  exitTo: "opacity-0",
}