svelte-reorderable-list
Version:
A simple and accessible reorderable list component for Svelte 5.
330 lines (246 loc) • 13.1 kB
Markdown
A simple and accessible reorderable list component for Svelte 5.
- Drag and drop to reorder items.
- Touch support for mobile devices.
- Accessibility support: keyboard navigation with Ctrl + Arrow keys.
- Horizontal and vertical lists.
- Customizable drag handle.
- Built with Svelte 5 runes.
```bash
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.
```svelte
<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>
{
<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
background-color:
}
</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:
```css
:root {
/* Focus and interaction colors */
--reorderable-focus-color:
--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:
--reorderable-keyboard-tip-color: white;
--reorderable-keyboard-tip-radius: 4px;
/* Drop indicators (for tree component) */
--reorderable-drop-indicator-color:
--reorderable-drop-indicator-radius: 2px;
--reorderable-drop-child-bg: rgba(0, 122, 204, 0.2);
}
```
```css
:root {
--reorderable-focus-color:
--reorderable-keyboard-tip-bg:
--reorderable-drop-indicator-color:
--reorderable-drag-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
```
```css
:root {
--reorderable-focus-color:
--reorderable-keyboard-tip-bg:
--reorderable-drop-indicator-color:
--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:
```svelte
<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>
{
<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:
```svelte
<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>
{
<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. |
```typescript
interface TreeNode<ItemType> {
item: ItemType;
children?: TreeNode<ItemType>[];
}
interface FlatTreeNode<ItemType> {
item: ItemType;
key: string;
parentKey?: string;
}
```
- Your data is naturally hierarchical (e.g., file systems, nested categories)
- You prefer working with nested object structures
- You need to maintain the tree structure in your existing data model
#### Use Flat Mode When:
- Your data comes from a database with parent-child relationships
- You need easier state management and updates
- You want to avoid deep nesting complexity
- You're working with dynamic hierarchies that change frequently
Both modes provide identical functionality and user experience - the choice is purely about data structure preference.
### Tree Keyboard Navigation
- Use `Tab` to focus on an item.
- `Ctrl + ↑/↓`: Move item up/down within the same level
- `Ctrl + ←`: Move item up one level (make it sibling of its parent)
- `Ctrl + →`: Make item a child of the previous item
## Accessibility
### General Accessibility Features
- Full keyboard navigation support
- ARIA attributes for screen readers
- Focus management with visible focus indicators
- Reduced motion support for users with vestibular disorders
- High contrast mode support
### Keyboard Navigation
#### List Component
- Use `Tab` to focus on an item.
- Once an item is focused, use `Ctrl + ArrowUp/ArrowDown` for vertical lists or `Ctrl + ArrowLeft/ArrowRight` for horizontal lists to move the item.
#### Tree Component
- Use `Tab` to focus on an item.
- Once an item is focused, use the keyboard shortcuts described above for tree navigation.