Logoreact-timeline-editor
Advanced Guides

Row & Cross-Row Drag

Enable drag-to-reorder entire tracks and drag action blocks between different rows.

The @keplar-404/react-timeline-editor introduces two powerful drag-and-drop features:

  1. Row Reordering (enableRowDrag) — drag entire tracks vertically
  2. Cross-Row Block Dragging (enableCrossRowDrag) — move action blocks between different rows

Live Preview — Row Reordering

💡 Drag the rows to reorder them

Row Reordering

Enable row drag sorting by setting enableRowDrag={true}. The engine calculates the new sorted order and provides it in onRowDragEnd:

import {
  Timeline,
  TimelineRow,
  TimelineEffect,
} from "@keplar-404/react-timeline-editor";
import React, { useState } from "react";

const mockData: TimelineRow[] = [
  { id: "Track A", actions: [] },
  { id: "Track B", actions: [] },
  { id: "Track C", actions: [] },
];

export const EditorRowDrag = () => {
  const [data, setData] = useState(mockData);

  return (
    <div style={{ width: "100%", height: "400px" }}>
      <Timeline
        editorData={data}
        effects={{}}
        onChange={setData}
        enableRowDrag={true}
        onRowDragStart={({ row }) => {
          console.log("Started dragging row:", row.id);
        }}
        onRowDragEnd={({ row, editorData }) => {
          // The engine provides the updated sorted array — just set it!
          setData(editorData as TimelineRow[]);
        }}
        style={{ width: "100%", height: "100%" }}
      />
    </div>
  );
};

The onRowDragEnd callback receives editorData — the new sorted array after the drop. You don't need to manually reorder the array; just call setData(editorData).

Cross-Row Block Dragging

Allow users to grab an action from one row and drop it into another:

💡 Drag action blocks between different tracks
export const EditorCrossRowDrag = () => {
  const [data, setData] = useState(mockData);

  return (
    <div style={{ width: "100%", height: "400px" }}>
      <Timeline
        editorData={data}
        effects={{}}
        onChange={setData}
        enableCrossRowDrag={true}
        style={{ width: "100%", height: "100%" }}
      />
    </div>
  );
};

During the drag, a ghost preview follows the cursor. By default it's a blue glowing box.

Custom Ghost Preview

Use getGhostPreview to render a fully custom ghost that matches your block's actual appearance:

<div style={{ width: "100%", height: "400px" }}>
  <Timeline
    editorData={data}
    effects={{}}
    onChange={setData}
    enableCrossRowDrag={true}
    getGhostPreview={({ action, row }) => (
      <div className="custom-ghost-preview">
        <span>Moving: {action.id}</span>
      </div>
    )}
    style={{ width: "100%", height: "100%" }}
  />
</div>
.custom-ghost-preview {
  background: #1a3a5c;
  border: 2px solid #3b82f6;
  height: 100%;
  border-radius: 4px;
  padding: 0 8px;
  display: flex;
  align-items: center;
  color: #3b82f6;
  font-size: 12px;
}

For the best user experience, render the same content in both getActionRender and getGhostPreview. This creates a smooth visual continuity between the real block and the ghost following the cursor.

On this page