A package to create accessible reorderable lists in Svelte.
Live demo:
npm install @rodrigodagostino/svelte-sortable-list
yarn install @rodrigodagostino/svelte-sortable-list
pnpm install @rodrigodagostino/svelte-sortable-list
<script lang="ts">
import { SortableList, sortItems, type SortableListProps } from 'svelte-sortable-list';
</script>
<script lang="ts">
import { SortableList, sortItems, type SortableListProps } from 'svelte-sortable-list';
import 'svelte-sortable-list/styles.css';
let items: SortableListProps['items'] = [
{
id: 1,
text: 'List item 1',
},
{
id: 2,
text: 'List item 2',
},
{
id: 3,
text: 'List item 3',
},
{
id: 4,
text: 'List item 4',
},
{
id: 5,
text: 'List item 5',
},
];
function handleSort(event: CustomEvent) {
const { prevIndex, nextIndex } = event.detail;
items = sortItems(items, prevIndex, nextIndex);
}
</script>
<SortableList {items} {...$props} let:item on:sort={handleSort}>
{item.text}
</SortableList>
The following is a list of steps to navigate and operate the Sortable List:
tab
to focus the list.arrow up
, arrow left
, arrow down
or arrow right
to focus the first item in the list.arrow up
or arrow left
to move the focus to the previous item.arrow down
or arrow right
to move the focus to the next item.space
to lift or drop an item.arrow up
or arrow left
to move the lifted item to the previous position.arrow down
or arrow right
to move the lifted item to the next position.escape
to cancel the lift and return the item to its initial position.Prop | Type | Default | Possible values | Description |
---|---|---|---|---|
items |
Object array | undefined |
Array of objects containing a mandatory id , an optional isDisabled and any other property to be used in the list items. |
List of items data. Each item must contain an id property and can contain an isDisabled property to make use of the disabled items feature. |
gap |
Number | 12 |
Number equal to or above 0 . |
Separation between items (in pixels). |
direction |
String | 'vertical' |
'vertical' or 'horizontal' |
Orientation in which items will be arranged. |
swapThreshold |
Number | 1 |
Number above 0 . |
Percentage of the target that the dragged item must cover to trigger the items swap. This value will be honored as long as there is only one item colliding with the dragged item. Otherwise, the item with most covered area by the dragged item will be marked as the target. |
transitionDuration |
Number | 320 |
Number equal to or above 0 . |
Time the swap transition takes to complete (milliseconds). Assign a value of 0 the remove animations. |
hasDropMarker |
Boolean | false |
true or false |
If true , displays a position marker representing where the dragged item will be positioned when drag-and-dropping. |
hasLockedAxis |
Boolean | false |
true or false |
If true , prevents the dragged item from moving away from the main axis. |
hasBoundaries |
Boolean | false |
true or false |
If true , items will only be draggable inside the list limits. |
hasRemoveOnDragOut |
Boolean | false |
true or false |
if true , items will be removed when dragged outside of the list boundaries. This needs to be accompanied by the on:remove event handler for it to work as expected. |
Slot props will give you access to the individual item data.
Prop | Provides access to |
---|---|
item |
Item data coming from the items prop. |
index |
Item index. |
Example:
<SortableList let:item let:index>
{index} - {item.text}
</SortableList>
Name | Type | Trigger | Returns |
---|---|---|---|
on:sort |
CustomEvent |
An item switches position. | event: { detail: { prevIndex: number, nextIndex: number } } } |
on:remove |
CustomEvent |
An item is removed. | event: { detail: { itemId: number } } } |
Function | Description |
---|---|
sortItems() |
Provides an easy mechanism to reorder items (in combination with the on:sort event). |
Example:
<script lang="ts">
import { sortItems } from 'svelte-sortable-list';
function handleSort(event: CustomEvent) {
const { prevIndex, nextIndex } = event.detail;
items = sortItems(items, prevIndex, nextIndex);
}
</script>
<SortableList {items} let:item on:sort={handleSort}>
{item.text}
</SortableList>
Name | Description |
---|---|
handle |
Activates the Handle feature. To do so, assign an element or component to the slot="handle" . |
remove |
Activates the Remove feature. To do so, assign an element or component to the slot="remove" . Make sure to include the on:remove event handler to properly run the item removal. |
Example:
<SortableList {items} let:item on:remove={handleRemove}>
<IconHandle slot="handle" />
{item.text}
<IconRemove slot="remove" />
</SortableList>
If you want to avoid bringing in your own icons, you can use the ones provided in the package:
Name | Description |
---|---|
Handle |
Handle icon. |
Remove |
Remove button icon. |
Example:
<script lang="ts">
import { IconHandle, IconRemove } from 'svelte-sortable-list';
</script>
<SortableList {items} let:item on:remove={handleRemove}>
<IconHandle slot="handle" />
{item.text}
<IconRemove slot="remove" />
</SortableList>
Type | Description |
---|---|
SortableListProps |
Provides definitions for your items prop. |
Example:
<script lang="ts">
import { type SortableListProps } from 'svelte-sortable-list';
let items: SortableListProps['items'] = [
{
id: 1,
text: 'List item 1',
isDisabled: false,
},
{
id: 2,
text: 'List item 2',
isDisabled: true,
},
{
id: 3,
text: 'List item 3',
isDisabled: true,
},
{
id: 4,
text: 'List item 4',
isDisabled: false,
},
{
id: 5,
text: 'List item 5',
isDisabled: false,
},
];
</script>
If you want to make use of the styles present in the demo pages, import them in your app like so:
import 'svelte-sortable-list/styles.css';
To customize the appearance of the list items and not cause any conflicts or interferences with the base styles and transitions, the usage of the .sortable-item
selector must be avoided, pointing instead to .sortable-item__inner
, which is the direct child of the aforementioned selector.
This is a list of the selectors you can use to style the list and the list items to your heart’s desire:
Selector | Points to |
---|---|
.sortable-list |
The main container element. |
.sortable-item |
Each of the list items. |
.sortable-item.is-dragging |
The item that is being dragged through a pointer. |
.sortable-item.is-dropping |
The item that is being dropped through a pointer. |
.sortable-item.is-selecting |
The item that is selected/lifted through keyboard. |
.sortable-item.is-deselecting |
The item that is being dropped through keyboard. |
.sortable-item[aria-disabled="true"] |
Each item that is disabled. |
.sortable-item__inner |
The item content wrapper element. |
.sortable-item__handle |
The Handle Slot container element. |
.sortable-item__content |
The Default Slot container element. |
.sortable-item__remove |
The Remove Slot container element. |
.ghost |
The shadow element displayed under the pointer when dragging. |
.ghost.is-dragging |
The shadow element while is being dragged. |
.ghost.is-dropping |
The shadow element while is being dropped. |
.ghost.is-between-bounds |
The shadow element while is inside the list limits. |
.ghost.is-out-of-bounds |
The shadow element while is outside the list limits. |
.ghost.is-removing |
The shadow element while the dragged item is being removed. |
.ghost.has-remove-on-drag-out |
The shadow element while the remove on drag out feature is enabled. |
In order to simplify its styles assignment, the Ghost element also receives the .sortable-item
class. This way, you can point to the .sortable-item
class and set the appearance of the Sortable Items and the Ghost at the same time.
While working on a SvelteKit app, I ran into the need of adding drag-and-drop capabilities to a couple of items lists, for which I decided to make use of SortableJS, which is certainly a popular option. I implemented it through a Svelte Action and it provided just what I needed, or so it seemed. After a while I realized I was not only missing touch screen support (since it was built with the HTML Drag and Drop API), but also accessibility was nowhere to be seen, and seems there are no plans to work on it.
I was not able to find any other suitable option, so this problem felt like a good opportunity to build my own package. And so while doing some research to try and understand the implications of such feature, I ran into a very interesting article and a very interesting talk by Vojtech Miksu which really guided me through the different paths available, their advantages, pain points and limitations to create a drag-and-drop system, putting particular focus on accessibility and touch screen support.
Even though React Movable was built for React, it served as my main inspiration when building this package. So thank you again, Vojtech :)