A simple and accessible reorderable list component for Svelte 5.
pnpm install svelte-reorderable-list
Use Tab
key to focus element and then Ctrl
+Arrows
to move.
Vertical Lists:
Horizontal Lists:
Here is a basic example of how to use the ReorderableList
component.
<script>
import ReorderableList from 'svelte-reorderable-list';
let items = [
{ id: '1', text: 'Item 1' },
{ id: '2', text: 'Item 2' },
{ id: '3', text: 'Item 3' },
{ id: '4', text: 'Item 4' },
];
// or items = $state([...])
const getKey = (item) => item.id;
function handleUpdate(updatedItems) {
items = updatedItems;
}
</script>
{#snippet item(item, index)}
<div class="item">
<span>{item.text}</span>
</div>
{/snippet}
<ReorderableList
items={items}
getKey={getKey}
onUpdate={handleUpdate}
item={item}
/>
<style>
.item {
padding: 1rem;
border: 1px solid #ccc;
background-color: #f9f9f9;
}
</style>
Prop | Type | Required | Default | Description |
---|---|---|---|---|
items |
ItemType[] |
Yes | undefined |
The array of items to be displayed. |
getKey |
(item: ItemType) => string |
Yes | undefined |
A function that returns a unique key for each item. |
item |
Snippet<[ItemType, number]> |
Yes | undefined |
A Svelte 5 snippet for rendering each item. It receives the item and its index. |
onUpdate |
(items: ItemType[]) => void |
Yes | undefined |
Callback function that is called with the new item order after a change. |
direction |
"horizontal" | "vertical" |
No | "vertical" |
The direction of the list. |
disabled |
boolean |
No | false |
When true , the reordering functionality is disabled. |
cssSelectorHandle |
string |
No | undefined |
A CSS selector for the drag handle. If not provided, the entire item is draggable. |
The components use CSS custom properties for theming. You can customize the appearance by overriding these variables in your CSS:
:root {
/* Focus and interaction colors */
--reorderable-focus-color: #007acc;
--reorderable-focus-offset: 2px;
--reorderable-focus-radius: 3px;
/* Drag clone appearance */
--reorderable-drag-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
--reorderable-drag-opacity: 0.6;
--reorderable-drag-scale: 1.02;
/* Keyboard tip styling */
--reorderable-keyboard-tip-bg: #016DB6;
--reorderable-keyboard-tip-color: white;
--reorderable-keyboard-tip-radius: 4px;
/* Drop indicators (for tree component) */
--reorderable-drop-indicator-color: #007acc;
--reorderable-drop-indicator-radius: 2px;
--reorderable-drop-child-bg: rgba(0, 122, 204, 0.2);
}
:root {
--reorderable-focus-color: #4fc3f7;
--reorderable-keyboard-tip-bg: #2196f3;
--reorderable-drop-indicator-color: #4fc3f7;
--reorderable-drag-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
:root {
--reorderable-focus-color: #6366f1;
--reorderable-keyboard-tip-bg: #4f46e5;
--reorderable-drop-indicator-color: #6366f1;
--reorderable-drop-child-bg: rgba(99, 102, 241, 0.1);
}
Use Tab
key to focus element and then Ctrl
+Arrows
to move.
The library also includes a ReorderableTree
component for hierarchical data. It supports two input modes:
<script>
import { ReorderableTree, type TreeNode } from 'svelte-reorderable-list';
let treeNodes: TreeNode<{ id: string; name: string; }>[] = [
{
item: { id: '1', name: 'Parent 1' },
children: [
{ item: { id: '1-1', name: 'Child 1.1' } },
{ item: { id: '1-2', name: 'Child 1.2' } }
]
},
{
item: { id: '2', name: 'Parent 2' },
children: [
{ item: { id: '2-1', name: 'Child 2.1' } }
]
}
];
const getKey = (item) => item.id;
function handleTreeUpdate(updatedNodes) {
treeNodes = updatedNodes;
}
</script>
{#snippet item(item, index)}
<div class="tree-item">
<span>{item.name}</span>
</div>
{/snippet}
<ReorderableTree
treeNodes={treeNodes}
getKey={getKey}
onUpdate={handleTreeUpdate}
item={item}
levelPadding="20px"
/>
For easier data management, you can also use a flat structure where hierarchy is defined by parentKey
references:
<script>
import { ReorderableTree, type FlatTreeNode } from 'svelte-reorderable-list';
let flatNodes: FlatTreeNode<{ id: string; name: string; }>[] = [
{ item: { id: '1', name: 'Parent 1' }, key: '1' },
{ item: { id: '1-1', name: 'Child 1.1' }, key: '1-1', parentKey: '1' },
{ item: { id: '1-2', name: 'Child 1.2' }, key: '1-2', parentKey: '1' },
{ item: { id: '2', name: 'Parent 2' }, key: '2' },
{ item: { id: '2-1', name: 'Child 2.1' }, key: '2-1', parentKey: '2' }
];
function handleFlatTreeUpdate(updatedNodes) {
flatNodes = updatedNodes;
}
</script>
{#snippet item(item, index)}
<div class="tree-item">
<span>{item.name}</span>
</div>
{/snippet}
<ReorderableTree
flatNodes={flatNodes}
onUpdate={handleFlatTreeUpdate}
item={item}
levelPadding="20px"
/>
The ReorderableTree
component automatically detects the input mode based on the props provided.
Prop | Type | Required | Default | Description |
---|---|---|---|---|
treeNodes |
TreeNode<ItemType>[] |
Yes | undefined |
The array of tree nodes to be displayed. |
getKey |
(item: ItemType) => string |
Yes | undefined |
A function that returns a unique key for each item. |
onUpdate |
(nodes: TreeNode<ItemType>[]) => void |
Yes | undefined |
Callback function that is called with the new tree structure after a change. |
Prop | Type | Required | Default | Description |
---|---|---|---|---|
flatNodes |
FlatTreeNode<ItemType>[] |
Yes | undefined |
The array of flat tree nodes with parentKey references. |
onUpdate |
(flatNodes: FlatTreeNode<ItemType>[]) => void |
Yes | undefined |
Callback function that is called with the new flat structure after a change. |
Prop | Type | Required | Default | Description |
---|---|---|---|---|
item |
Snippet<[ItemType, number]> |
Yes | undefined |
A Svelte 5 snippet for rendering each item. It receives the item and its index. |
disabled |
boolean |
No | false |
When true , the reordering functionality is disabled. |
cssSelectorHandle |
string |
No | undefined |
A CSS selector for the drag handle. If not provided, the entire item is draggable. |
levelPadding |
string |
No | "20px" |
CSS padding value for each nesting level. |
interface TreeNode<ItemType> {
item: ItemType;
children?: TreeNode<ItemType>[];
}
interface FlatTreeNode<ItemType> {
item: ItemType;
key: string;
parentKey?: string;
}
Both modes provide identical functionality and user experience - the choice is purely about data structure preference.
Tab
to focus on an item.Ctrl + ↑/↓
: Move item up/down within the same levelCtrl + ←
: Move item up one level (make it sibling of its parent)Ctrl + →
: Make item a child of the previous itemTab
to focus on an item.Ctrl + ArrowUp/ArrowDown
for vertical lists or Ctrl + ArrowLeft/ArrowRight
for horizontal lists to move the item.Tab
to focus on an item.