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:
- Capture BEFORE positions of all registered elements
- Position:absolute on exiting element with parent compensation
- Synchronous reflow via
getBoundingClientRect()(prevents flicker) - Capture AFTER positions
- Calculate INVERT deltas with
flipBehaviorfiltering - Per-token stagger exit animation (opacity/scale/blur)
- Promise.all wait for all animations
- 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
| Layout | Description |
|---|---|
auto | Automatic based on container |
horizontal | Flex row with wrap |
inline-horizontal | Inline-flex row no-wrap |
vertical | Flex column |
grid | CSS grid with auto-fill |
FLIP Behavior
Control which elements animate when a sibling exits:
| Behavior | Description |
|---|---|
all | All remaining elements animate to new positions |
siblings-after | Only elements after the exiting item animate |
none | No FLIP animation, just exit/enter transitions |
Exit Position Strategy
Control how exiting items are positioned during animation:
| Strategy | Description |
|---|---|
absolute-fixed | Position:absolute with parent compensation (default) |
in-place | No positioning change |
custom | Manual 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
| Prop | Type | Default | Description |
|---|---|---|---|
layout | 'auto' | 'horizontal' | 'inline-horizontal' | 'vertical' | 'grid' | 'auto' | Layout direction |
stagger | number | { enter: number; exit: number } | 12 | Stagger delay (ms) |
duration | number | { 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) => void | — | Callback on exit |
onItemEnter | (id: string) => void | — | Callback on enter |
children | ReactNode | — | Items with unique key props |
useReorder Config
| Option | Type | Default | Description |
|---|---|---|---|
enterDuration | number | 300 | Enter animation duration |
exitDuration | number | 200 | Exit animation duration |
flipDuration | number | 300 | FLIP animation duration |
flipBehavior | FLIPBehavior | 'all' | FLIP behavior |
exitPositionStrategy | ExitPositionStrategy | 'absolute-fixed' | Exit strategy |
onComplete | (id: string) => void | — | Exit complete callback |
useReorder Return
| Property | Type | Description |
|---|---|---|
registerElement | (id, element) => void | Register element for FLIP |
unregisterElement | (id) => void | Remove from registry |
startExit | (id) => Promise<void> | Trigger exit animation |
startEnter | (id) => Promise<void> | Trigger enter animation |
isAnimating | (id?) => boolean | Check if animating |
registry | Map<string, HTMLElement> | Element registry |
Related
- SlidingText - Character/word animations
- Morph - Shape morphing transitions
- useListFormat - Locale-aware list formatting