<!-- Source: https://motion.svelte.page/examples/reorder -->

# Reorder

> Drag-to-reorder lists and wrapped grids with automatic axis detection, RTL-aware insertion, FLIP siblings, and edge auto-scroll.

**Source:** [https://motion.svelte.page/examples/reorder](https://motion.svelte.page/examples/reorder)

**Markdown mirror:** [https://motion.svelte.page/examples/reorder.md](https://motion.svelte.page/examples/reorder.md)

---

This mirror preserves the prose, implementation notes, and runnable Svelte source behind the live example page.

## FIG-001: drag to reorder.

The motion.dev grocery list. Grab any item and drag it vertically — the dragged item pins under the cursor while its siblings spring out of the way, and releasing snaps it into its new slot.

**Metadata:** tag: `GESTURE` | pattern: `reorder-group`

### Notes

- `Reorder.Group` owns the order: it watches each drag and, when an item's edge crosses the midpoint of a neighbor, calls `onReorder` with the swapped array. Assigning that back to `$state` is the whole wiring.
- `axis="y"` locks item drags vertically. Items key on their own value in the `{#each}` block — the same value passed to `Reorder.Item`'s `value` prop.
- `whileDrag` passes straight through to the underlying motion component — the lifted item scales up and gains a shadow while the gesture is active.

### Source

#### Default.svelte

Source file: [src/lib/examples/reorder/demos/Default.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/reorder/demos/Default.svelte)

```svelte
<script lang="ts">
    import { Reorder, styleString } from '@humanspeak/svelte-motion'

    // The motion.dev Reorder demo: drag items vertically to reorder the
    // list. Dragged items pin under the cursor while siblings FLIP out
    // of the way; releasing springs the item into its new slot.

    let items = $state(['🍅 Tomato', '🥒 Cucumber', '🧀 Cheese', '🥬 Lettuce'])

    const itemStyle = styleString(() => ({
        display: 'flex',
        alignItems: 'center',
        height: '44px',
        padding: '0 16px',
        marginBottom: '10px',
        background: 'var(--brut-bg-2, #eef4f1)',
        color: 'var(--brut-ink, #0a0a0a)',
        border: '1px solid var(--brut-rule-2, #bbc4c0)',
        boxShadow: '4px 4px 0 var(--brut-rule, #d6dedb)',
        cursor: 'grab',
        userSelect: 'none',
        fontFamily: 'var(--brut-mono, monospace)',
        fontSize: '0.8125rem',
        fontWeight: 700
    }))
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// reorder</span>
            <span class="micro readout">drag to sort</span>
        </div>

        <Reorder.Group
            axis="y"
            values={items}
            onReorder={(next: string[]) => (items = next)}
            style={styleString(() => ({
                listStyle: 'none',
                padding: 0,
                margin: 0,
                width: '260px'
            }))}
        >
            {#each items as item (item)}
                <Reorder.Item value={item} whileDrag={{ scale: 1.03 }} style={itemStyle}>
                    {item}
                </Reorder.Item>
            {/each}
        </Reorder.Group>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 2rem;
        min-height: 320px;
    }

    .strip {
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .strip-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        width: 260px;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .micro {
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .readout {
        color: var(--brut-accent, #247768);
    }
</style>
```

## FIG-002: reorder in two dimensions.

Drag one tile horizontally and vertically across wrapped rows. Every displaced tile springs into its new slot during the same continuous gesture.

**Metadata:** tag: `GRID` | axis: `xy`

### Notes

- `axis="xy"` clusters overlapping boxes into visual rows and chooses the nearest row and horizontal insertion slot as the tile moves.
- One uninterrupted gesture can cross both axes while keyed siblings FLIP into every newly vacated slot.
- The consumer still owns one flat `values` array; Reorder maps the visual wrapped layout back to that logical order.

### Source

#### Grid.svelte

Source file: [src/lib/examples/reorder/demos/Grid.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/reorder/demos/Grid.svelte)

```svelte
<script lang="ts">
    import { Reorder, styleString } from '@humanspeak/svelte-motion'

    const colors: Record<string, string> = {
        A: '#ff6b6b',
        B: '#ffd166',
        C: '#71d99e',
        D: '#58bde8',
        E: '#8f8df4',
        F: '#d18aef'
    }

    let items = $state(['A', 'B', 'C', 'D', 'E', 'F'])

    const groupStyle = styleString(() => ({
        display: 'flex',
        flexWrap: 'wrap',
        gap: '10px',
        width: '260px',
        margin: 0,
        padding: 0,
        listStyle: 'none'
    }))

    const itemStyle = (item: string) =>
        styleString(() => ({
            display: 'grid',
            placeItems: 'center',
            width: '80px',
            height: '80px',
            border: '1px solid var(--brut-rule, #0a0a0a)',
            background: colors[item],
            color: '#0a0a0a',
            boxShadow: '4px 4px 0 var(--brut-rule, #0a0a0a)',
            cursor: 'grab',
            userSelect: 'none',
            fontFamily: 'var(--brut-mono, monospace)',
            fontSize: '1.125rem',
            fontWeight: 800
        }))
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// reorder — xy</span>
            <span class="micro readout">{items.join(' · ')}</span>
        </div>

        <Reorder.Group
            axis="xy"
            values={items}
            onReorder={(next: string[]) => (items = next)}
            style={groupStyle}
        >
            {#each items as item (item)}
                <Reorder.Item
                    value={item}
                    whileDrag={{ scale: 1.08 }}
                    transition={{ type: 'spring', stiffness: 500, damping: 35 }}
                    style={itemStyle(item)}
                >
                    {item}
                </Reorder.Item>
            {/each}
        </Reorder.Group>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        min-height: 300px;
        align-items: center;
        justify-content: center;
        padding: 2rem;
    }

    .strip {
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .strip-head {
        display: flex;
        width: 260px;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .micro {
        color: var(--brut-ink-3, #9a9a9a);
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }

    .readout {
        color: var(--brut-accent, #247768);
    }
</style>
```

## FIG-003: detect direction, respect intent.

This horizontal group omits axis and renders RTL. Geometry selects horizontal dragging automatically, and visual leftward movement advances the logical data order without reversing the array.

**Metadata:** tag: `RTL` | axis: `auto`

### Notes

- Omitting `axis` lets measured geometry select `x`, `y`, or `xy`; no orientation prop is needed for this row.
- Direction comes from the group's computed style, so nested RTL regions behave correctly without changing document direction.
- Consumers keep values in logical order. Reorder interprets horizontal insertion in the visual direction instead of asking you to reverse the array.

### Source

#### AutoAxisRtl.svelte

Source file: [src/lib/examples/reorder/demos/AutoAxisRtl.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/reorder/demos/AutoAxisRtl.svelte)

```svelte
<script lang="ts">
    import { Reorder, styleString } from '@humanspeak/svelte-motion'

    let items = $state(['Aleph', 'Bet', 'Gimel', 'Dalet'])

    const groupStyle = styleString(() => ({
        display: 'flex',
        gap: '8px',
        margin: 0,
        padding: 0,
        listStyle: 'none',
        direction: 'rtl'
    }))

    const itemStyle = styleString(() => ({
        display: 'grid',
        placeItems: 'center',
        width: '72px',
        height: '72px',
        border: '1px solid var(--brut-rule, #0a0a0a)',
        background: 'var(--brut-bg-2, #eef4f1)',
        color: 'var(--brut-ink, #0a0a0a)',
        boxShadow: '3px 3px 0 var(--brut-rule, #0a0a0a)',
        cursor: 'grab',
        userSelect: 'none',
        fontFamily: 'var(--brut-mono, monospace)',
        fontSize: '0.75rem',
        fontWeight: 700
    }))
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// axis — auto</span>
            <span class="micro readout">dir: rtl</span>
        </div>

        <Reorder.Group
            values={items}
            onReorder={(next: string[]) => (items = next)}
            style={groupStyle}
        >
            {#each items as item (item)}
                <Reorder.Item value={item} whileDrag={{ scale: 1.05 }} style={itemStyle}>
                    {item}
                </Reorder.Item>
            {/each}
        </Reorder.Group>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        min-height: 240px;
        align-items: center;
        justify-content: center;
        padding: 2rem;
    }

    .strip {
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .strip-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .micro {
        color: var(--brut-ink-3, #9a9a9a);
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }

    .readout {
        color: var(--brut-accent, #247768);
    }
</style>
```

## FIG-004: reordering long lists.

Ten items in a 280px scroll container. Drag an item toward the top or bottom edge and hold — the container auto-scrolls beneath the held item so a single gesture can carry it the length of the list.

**Metadata:** tag: `SCROLL` | pattern: `auto-scroll`

### Notes

- The scroll container is a `motion.div` with `layoutScroll`, so layout measurements are taken in the container's coordinate space and stay correct at any scroll position.
- Holding a dragged item within 50px of the container's edge auto-scrolls it — speed ramps up quadratically as the pointer nears the edge, and scrolling only starts when the gesture is moving toward that edge.
- Items keep reordering while the content scrolls beneath the held pointer, so one gesture can carry an item from the top of the list to the bottom.

### Source

#### Scrollable.svelte

Source file: [src/lib/examples/reorder/demos/Scrollable.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/reorder/demos/Scrollable.svelte)

```svelte
<script lang="ts">
    import { motion, Reorder, styleString } from '@humanspeak/svelte-motion'

    // A long list inside a fixed-height scroll container. `layoutScroll`
    // keeps layout measurements correct while the container scrolls, and
    // dragging an item near the top or bottom edge auto-scrolls the list
    // so the whole thing can be traversed in one gesture.

    let items = $state([
        'Aardvark',
        'Beaver',
        'Capybara',
        'Dingo',
        'Echidna',
        'Fennec',
        'Gecko',
        'Hedgehog',
        'Iguana',
        'Jackal'
    ])

    const itemStyle = styleString(() => ({
        display: 'flex',
        alignItems: 'center',
        height: '42px',
        padding: '0 16px',
        marginBottom: '8px',
        background: 'var(--brut-bg-2, #eef4f1)',
        color: 'var(--brut-ink, #0a0a0a)',
        border: '1px solid var(--brut-rule-2, #bbc4c0)',
        boxShadow: '4px 4px 0 var(--brut-rule, #d6dedb)',
        cursor: 'grab',
        userSelect: 'none',
        fontFamily: 'var(--brut-mono, monospace)',
        fontSize: '0.8125rem',
        fontWeight: 700
    }))
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// reorder — scrollable</span>
            <span class="micro readout">{items.length} items</span>
        </div>

        <motion.div
            layoutScroll
            style={styleString(() => ({
                height: '280px',
                width: '260px',
                overflowY: 'scroll',
                padding: '10px',
                border: '1px solid var(--brut-rule-2, #bbc4c0)',
                background: 'var(--brut-bg, #f8fcfb)'
            }))}
        >
            <Reorder.Group
                axis="y"
                values={items}
                onReorder={(next: string[]) => (items = next)}
                style={styleString(() => ({
                    listStyle: 'none',
                    padding: 0,
                    margin: 0
                }))}
            >
                {#each items as item (item)}
                    <Reorder.Item value={item} whileDrag={{ scale: 1.03 }} style={itemStyle}>
                        {item}
                    </Reorder.Item>
                {/each}
            </Reorder.Group>
        </motion.div>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 2rem;
        min-height: 360px;
    }

    .strip {
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .strip-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        width: 260px;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .micro {
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .readout {
        color: var(--brut-accent, #247768);
        font-variant-numeric: tabular-nums;
    }
</style>
```
