Skip to main content
The Reorder components enable drag-to-reorder functionality with automatic layout animations. Use Reorder.Group to create a reorderable container and Reorder.Item for each draggable element.

Usage

import { Reorder } from "motion/react"
import { useState } from "react"

export function App() {
  const [items, setItems] = useState([0, 1, 2, 3])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      {items.map(item => (
        <Reorder.Item key={item} value={item">
          {item}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

Components

Reorder.Group

Container component that manages reordering state and coordinates layout animations.
<Reorder.Group
  axis="y"
  values={items}
  onReorder={setItems}
>
  {/* Reorder.Item components */}
</Reorder.Group>

Reorder.Item

Draggable item component that can be reordered within a Reorder.Group.
<Reorder.Item key={item.id} value={item">
  <div>Drag me</div>
</Reorder.Item>

Reorder.Group Props

values
T[]
required
Array of values in the current order. Must be kept in sync with your state.
const [items, setItems] = useState([1, 2, 3])

<Reorder.Group values={items} onReorder={setItems">
  {items.map(item => (
    <Reorder.Item key={item} value={item} />
  ))}
</Reorder.Group>
onReorder
(newOrder: T[]) => void
required
Callback fired when items are reordered. Receives the new order array.Typically, this is your state setter function.
const [items, setItems] = useState([1, 2, 3])

<Reorder.Group 
  values={items}
  onReorder={setItems}
/>
axis
x or y
default:"y"
The axis to reorder along. Items will be draggable on this axis by default.Set individual items to drag to allow dragging on both axes.
// Vertical list
<Reorder.Group axis="y" values={items} onReorder={setItems} />

// Horizontal list
<Reorder.Group axis="x" values={items} onReorder={setItems} />
as
keyof HTMLElements
default:"ul"
HTML element to render as. Defaults to "ul".
<Reorder.Group as="div" values={items} onReorder={setItems} />
<Reorder.Group as="ol" values={items} onReorder={setItems} />
All HTMLMotionProps are also supported (e.g., style, className, event handlers).

Reorder.Item Props

value
T
required
The value this item represents in the values array. Used to track and reorder items.
<Reorder.Item value={item} />
as
keyof HTMLElements
default:"li"
HTML element to render as. Defaults to "li".
<Reorder.Item as="div" value={item} />
layout
true or position
default:"true"
Enable layout animations. Set to "position" to only animate position changes, not size.
// Animate position and size
<Reorder.Item value={item} layout />

// Animate position only
<Reorder.Item value={item} layout="position" />
All HTMLMotionProps are also supported, including drag gesture props.

Examples

Basic Vertical List

import { Reorder } from "motion/react"
import { useState } from "react"

function TodoList() {
  const [items, setItems] = useState([
    "Task 1",
    "Task 2",
    "Task 3"
  ])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      {items.map(item => (
        <Reorder.Item 
          key={item} 
          value={item}
          style={{
            padding: 16,
            background: 'white',
            marginBottom: 8,
            borderRadius: 8,
            cursor: 'grab'
          }}
        >
          {item}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

Horizontal List

function HorizontalReorder() {
  const [items, setItems] = useState([1, 2, 3, 4, 5])

  return (
    <Reorder.Group 
      axis="x" 
      values={items} 
      onReorder={setItems}
      style={{ display: 'flex', gap: 8 }}
    >
      {items.map(item => (
        <Reorder.Item
          key={item}
          value={item}
          style={{
            width: 100,
            height: 100,
            background: '#eee',
            borderRadius: 8,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
          }}
        >
          {item}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

Complex Items

function ComplexList() {
  const [items, setItems] = useState([
    { id: 1, title: 'Item 1', description: 'Description 1' },
    { id: 2, title: 'Item 2', description: 'Description 2' },
    { id: 3, title: 'Item 3', description: 'Description 3' }
  ])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      {items.map(item => (
        <Reorder.Item
          key={item.id}
          value={item}
          style={{
            padding: 20,
            background: 'white',
            marginBottom: 12,
            borderRadius: 12,
            cursor: 'grab'
          }}
        >
          <h3>{item.title}</h3>
          <p>{item.description}</p>
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

With Custom Drag Handle

import { Reorder, useDragControls } from "motion/react"

function Item({ item }) {
  const controls = useDragControls()

  return (
    <Reorder.Item
      value={item}
      dragListener={false}
      dragControls={controls}
      style={{ padding: 16, background: 'white' }}
    >
      <div style={{ display: 'flex', gap: 12 }">
        <button
          onPointerDown={(e) => controls.start(e)}
          style={{ cursor: 'grab' }}
        >
          ⋮⋮
        </button>
        <span>{item}</span>
      </div>
    </Reorder.Item>
  )
}

function List() {
  const [items, setItems] = useState(["A", "B", "C"])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      {items.map(item => (
        <Item key={item} item={item} />
      ))}
    </Reorder.Group>
  )
}

With Animation on Drag

function AnimatedReorder() {
  const [items, setItems] = useState([1, 2, 3])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      {items.map(item => (
        <Reorder.Item
          key={item}
          value={item}
          whileDrag={{ 
            scale: 1.05,
            boxShadow: '0 10px 30px rgba(0,0,0,0.15)'
          }}
          style={{
            padding: 20,
            background: 'white',
            marginBottom: 8,
            borderRadius: 8
          }}
        >
          {item}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

Grid Layout (2D Reordering)

function GridReorder() {
  const [items, setItems] = useState([1, 2, 3, 4, 5, 6])

  return (
    <Reorder.Group
      axis="y"
      values={items}
      onReorder={setItems}
      style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(3, 1fr)',
        gap: 16
      }}
    >
      {items.map(item => (
        <Reorder.Item
          key={item}
          value={item}
          drag // Allow dragging in both directions
          style={{
            width: '100%',
            aspectRatio: '1',
            background: '#eee',
            borderRadius: 8,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
          }}
        >
          {item}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

With Remove Animation

import { Reorder, AnimatePresence } from "motion/react"

function RemovableList() {
  const [items, setItems] = useState([1, 2, 3, 4])

  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems">
      <AnimatePresence>
        {items.map(item => (
          <Reorder.Item
            key={item}
            value={item}
            exit={{ opacity: 0, x: -100 }}
            style={{ padding: 16, background: 'white' }}
          >
            <div style={{ display: 'flex', justifyContent: 'space-between' }">
              {item}
              <button onClick={() => setItems(items.filter(i => i !== item))">
                Remove
              </button>
            </div>
          </Reorder.Item>
        ))}
      </AnimatePresence>
    </Reorder.Group>
  )
}

TypeScript

import { Reorder } from "motion/react"
import type { HTMLMotionProps } from "motion/react"

interface Item {
  id: number
  name: string
}

function List() {
  const [items, setItems] = useState<Item[]>([])

  return (
    <Reorder.Group<Item>
      axis="y"
      values={items}
      onReorder={setItems}
    >
      {items.map(item => (
        <Reorder.Item<Item>
          key={item.id}
          value={item}
        >
          {item.name}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  )
}

// Props types
type GroupProps<T> = {
  axis?: 'x' | 'y'
  values: T[]
  onReorder: (newOrder: T[]) => void
  as?: keyof HTMLElements
} & HTMLMotionProps<'ul'>

type ItemProps<T> = {
  value: T
  as?: keyof HTMLElements
  layout?: true | 'position'
} & HTMLMotionProps<'li'>

How It Works

  1. Reorder.Group tracks the current positions of all items
  2. When an item is dragged, it calculates its new position
  3. The onReorder callback fires with the new array order
  4. Layout animations smoothly transition items to their new positions
  5. The component uses layoutId internally for smooth reordering

Notes

  • Each Reorder.Item must have a unique key prop
  • The value prop must reference the actual item in the values array
  • Items are draggable only along the specified axis by default
  • Set drag prop on Reorder.Item to enable dragging in both directions
  • Auto-scrolling is enabled when dragging near container edges
  • Works with AnimatePresence for enter/exit animations

Build docs developers (and LLMs) love