@mks2508/mks-ui
Primitives

Reorder

FLIP-based list animation primitive for smooth item reordering with enter/exit transitions.

Reorder

A container component that provides FLIP (First-Last-Invert-Play) animations for reorderable lists. Items smoothly animate when siblings are added, removed, or reordered.

Import

import { Reorder, useReorder, useReorderPresence } from '@mks2508/mks-ui/react';

How It Works

The Reorder component implements a full 8-step FLIP animation sequence:

  1. Capture BEFORE positions of all registered elements
  2. Position:absolute on exiting element with parent compensation
  3. Synchronous reflow via getBoundingClientRect() (prevents flicker)
  4. Capture AFTER positions
  5. Calculate INVERT deltas with flipBehavior filtering
  6. Per-token stagger exit animation (opacity/scale/blur)
  7. Promise.all wait for all animations
  8. Cleanup remove positioning, call onExitComplete

Basic Usage

Horizontal List
.tsx
<Reorder layout="horizontal" stagger={15}>
{items.map(item => (
  <div key={item.id} data-reorder-id={item.id} className="px-3 py-2 bg-white/10 rounded">
    {item.text}
  </div>
))}
</Reorder>

Layout Variants

LayoutDescription
autoAutomatic based on container
horizontalFlex row with wrap
inline-horizontalInline-flex row no-wrap
verticalFlex column
gridCSS grid with auto-fill

FLIP Behavior

Control which elements animate when a sibling exits:

BehaviorDescription
allAll remaining elements animate to new positions
siblings-afterOnly elements after the exiting item animate
noneNo FLIP animation, just exit/enter transitions

Exit Position Strategy

Control how exiting items are positioned during animation:

StrategyDescription
absolute-fixedPosition:absolute with parent compensation (default)
in-placeNo positioning change
customManual positioning via callback

useReorder Hook

For imperative control over animations:

import { useReorder } from '@mks2508/mks-ui/react';

function ManagedList() {
  const { registerElement, startItemExit, isAnimating } = useReorder({
    exitDuration: 200,
    flipDuration: 300,
    onComplete: (id) => removeItem(id),
  });

  const handleRemove = async (id) => {
    await startItemExit(id);
  };

  return (
    <div>
      {items.map(item => (
        <div
          key={item.id}
          ref={(el) => registerElement(item.id, el)}
          data-reorder-id={item.id}
        >
          {item.text}
          <button onClick={() => handleRemove(item.id)}>×</button>
        </div>
      ))}
    </div>
  );
}

useReorderPresence Hook

Manage presence animations for enter/exit:

import { useReorderPresence } from '@mks2508/mks-ui/react';

const presence = useReorderPresence({
  enterStagger: 30,
  exitStagger: 20,
  onExitComplete: (id) => removeItem(id),
});

// Get children with data-reorder-state attributes
const presentChildren = presence.getPresentChildren(children);

// Trigger exit manually
await presence.triggerExit(itemId);

// Query state
presence.isExiting(id);  // boolean
presence.isEntering(id); // boolean
presence.exitingIds;     // Set<string>

CSS State Machine

Items receive data-reorder-state attributes for CSS styling:

[data-reorder-state="idle"]      /* Default state */
[data-reorder-state="entering"]  /* Enter animation */
[data-reorder-state="exiting"]   /* Exit animation */
[data-reorder-state="flipping"]  /* FLIP in progress */
[data-reorder-state="completed"] /* Animation done */

API Reference

Reorder Props

PropTypeDefaultDescription
layout'auto' | 'horizontal' | 'inline-horizontal' | 'vertical' | 'grid''auto'Layout direction
staggernumber | { enter: number; exit: number }12Stagger delay (ms)
durationnumber | { enter: number; exit: number }{ enter: 300, exit: 200 }Animation duration (ms)
flipBehavior'all' | 'siblings-after' | 'none''all'Which elements FLIP
exitPositionStrategy'absolute-fixed' | 'in-place' | 'custom''absolute-fixed'Exit positioning
onItemExit(id: string) => voidCallback on exit
onItemEnter(id: string) => voidCallback on enter
childrenReactNodeItems with unique key props

useReorder Config

OptionTypeDefaultDescription
enterDurationnumber300Enter animation duration
exitDurationnumber200Exit animation duration
flipDurationnumber300FLIP animation duration
flipBehaviorFLIPBehavior'all'FLIP behavior
exitPositionStrategyExitPositionStrategy'absolute-fixed'Exit strategy
onComplete(id: string) => voidExit complete callback

useReorder Return

PropertyTypeDescription
registerElement(id, element) => voidRegister element for FLIP
unregisterElement(id) => voidRemove from registry
startExit(id) => Promise<void>Trigger exit animation
startEnter(id) => Promise<void>Trigger enter animation
isAnimating(id?) => booleanCheck if animating
registryMap<string, HTMLElement>Element registry