LayoutExtension
Sortable
A list that people reorder by dragging a grip handle or with the keyboard, with each move announced to screen readers.
| Class name | Type | Description |
|---|---|---|
sortableplf:sortable | Component | The reorderable list |
sortable-itemplf:sortable-item | Part | An item in the list |
sortable-handleplf:sortable-handle | Part | The grip button that starts a move |
sortable-iconplf:sortable-icon | Part | Your own SVG, in place of the drawn grip; an icon class there doesn't need it |
sortable-nameplf:sortable-name | Part | The item's text |
sortable-metaplf:sortable-meta | Part | Secondary details at the end |
sortable-mediaplf:sortable-media | Part | A media item's square image |
sortable-item-mediaplf:sortable-item-media | Modifier | An item with an image above its name |
Default
Name the list with aria-label or aria-labelledby. Each item’s button is its handle, the only place a move starts: name it after the item, such as “Reorder Item one”.
- Item one
- Item two
- Item three
<ul class="sortable" data-plf-sortable aria-label="Tasks">
<li class="sortable-item" data-plf-value="one">
<button class="sortable-handle" aria-label="Reorder Item one"></button>
<span class="sortable-name" data-plf-sortable-name>Item one</span>
<span class="sortable-meta">Today</span>
</li>
<li class="sortable-item" data-plf-value="two">
<button class="sortable-handle" aria-label="Reorder Item two"></button>
<span class="sortable-name" data-plf-sortable-name>Item two</span>
<span class="sortable-meta">Tomorrow</span>
</li>
<li class="sortable-item" data-plf-value="three">
<button class="sortable-handle" aria-label="Reorder Item three"></button>
<span class="sortable-name" data-plf-sortable-name>Item three</span>
<span class="sortable-meta">Friday</span>
</li>
</ul><ul class="plf:sortable" data-plf-sortable aria-label="Tasks">
<li class="plf:sortable-item" data-plf-value="one">
<button class="plf:sortable-handle" aria-label="Reorder Item one"></button>
<span class="plf:sortable-name" data-plf-sortable-name>Item one</span>
<span class="plf:sortable-meta">Today</span>
</li>
<li class="plf:sortable-item" data-plf-value="two">
<button class="plf:sortable-handle" aria-label="Reorder Item two"></button>
<span class="plf:sortable-name" data-plf-sortable-name>Item two</span>
<span class="plf:sortable-meta">Tomorrow</span>
</li>
<li class="plf:sortable-item" data-plf-value="three">
<button class="plf:sortable-handle" aria-label="Reorder Item three"></button>
<span class="plf:sortable-name" data-plf-sortable-name>Item three</span>
<span class="plf:sortable-meta">Friday</span>
</li>
</ul>- One
- Two
- Three
- Four
<ul class="sortable" data-plf-sortable data-plf-orientation="horizontal" aria-label="Topics">
<li class="sortable-item" data-plf-value="one">
<button class="sortable-handle" aria-label="Reorder One"></button>
<span class="sortable-name">One</span>
</li>
<li class="sortable-item" data-plf-value="two">
<button class="sortable-handle" aria-label="Reorder Two"></button>
<span class="sortable-name">Two</span>
</li>
<li class="sortable-item" data-plf-value="three">
<button class="sortable-handle" aria-label="Reorder Three"></button>
<span class="sortable-name">Three</span>
</li>
<li class="sortable-item" data-plf-value="four">
<button class="sortable-handle" aria-label="Reorder Four"></button>
<span class="sortable-name">Four</span>
</li>
</ul><ul class="plf:sortable" data-plf-sortable data-plf-orientation="horizontal" aria-label="Topics">
<li class="plf:sortable-item" data-plf-value="one">
<button class="plf:sortable-handle" aria-label="Reorder One"></button>
<span class="plf:sortable-name">One</span>
</li>
<li class="plf:sortable-item" data-plf-value="two">
<button class="plf:sortable-handle" aria-label="Reorder Two"></button>
<span class="plf:sortable-name">Two</span>
</li>
<li class="plf:sortable-item" data-plf-value="three">
<button class="plf:sortable-handle" aria-label="Reorder Three"></button>
<span class="plf:sortable-name">Three</span>
</li>
<li class="plf:sortable-item" data-plf-value="four">
<button class="plf:sortable-handle" aria-label="Reorder Four"></button>
<span class="plf:sortable-name">Four</span>
</li>
</ul>Grid
Keep draggable="false" on each image, or Firefox drags the picture instead of moving the item.
-
Photo one
-
Photo two
-
Photo three
<ol class="sortable" data-plf-sortable data-plf-orientation="grid" aria-label="Photos">
<li class="sortable-item sortable-item-media" data-plf-value="one">
<img class="sortable-media" src="/previews/assets/gallery-desert.svg" alt="Desert dunes" draggable="false">
<button class="sortable-handle" aria-label="Reorder Photo one"></button>
<span class="sortable-name">Photo one</span>
</li>
<li class="sortable-item sortable-item-media" data-plf-value="two">
<img class="sortable-media" src="/previews/assets/gallery-garden.svg" alt="A garden in bloom" draggable="false">
<button class="sortable-handle" aria-label="Reorder Photo two"></button>
<span class="sortable-name">Photo two</span>
</li>
<li class="sortable-item sortable-item-media" data-plf-value="three">
<img class="sortable-media" src="/previews/assets/gallery-venue.svg" alt="An event venue" draggable="false">
<button class="sortable-handle" aria-label="Reorder Photo three"></button>
<span class="sortable-name">Photo three</span>
</li>
</ol><ol class="plf:sortable" data-plf-sortable data-plf-orientation="grid" aria-label="Photos">
<li class="plf:sortable-item plf:sortable-item-media" data-plf-value="one">
<img class="plf:sortable-media" src="/previews/assets/gallery-desert.svg" alt="Desert dunes" draggable="false">
<button class="plf:sortable-handle" aria-label="Reorder Photo one"></button>
<span class="plf:sortable-name">Photo one</span>
</li>
<li class="plf:sortable-item plf:sortable-item-media" data-plf-value="two">
<img class="plf:sortable-media" src="/previews/assets/gallery-garden.svg" alt="A garden in bloom" draggable="false">
<button class="plf:sortable-handle" aria-label="Reorder Photo two"></button>
<span class="plf:sortable-name">Photo two</span>
</li>
<li class="plf:sortable-item plf:sortable-item-media" data-plf-value="three">
<img class="plf:sortable-media" src="/previews/assets/gallery-venue.svg" alt="An event venue" draggable="false">
<button class="plf:sortable-handle" aria-label="Reorder Photo three"></button>
<span class="plf:sortable-name">Photo three</span>
</li>
</ol>Saving the order
Sortable moves the real items, so their hidden inputs submit in the new order; to save after every move, read event.detail.order in plf:sortable:change.
<form class="flex flex-col gap-4" id="sortable-form">
<ol class="sortable" data-plf-sortable aria-label="Steps">
<li class="sortable-item" data-plf-value="one">
<button class="sortable-handle" type="button" aria-label="Reorder Step one"></button>
<span class="sortable-name">Step one</span>
<input type="hidden" name="steps" value="one">
</li>
<li class="sortable-item" data-plf-value="two">
<button class="sortable-handle" type="button" aria-label="Reorder Step two"></button>
<span class="sortable-name">Step two</span>
<input type="hidden" name="steps" value="two">
</li>
<li class="sortable-item" data-plf-value="three">
<button class="sortable-handle" type="button" aria-label="Reorder Step three"></button>
<span class="sortable-name">Step three</span>
<input type="hidden" name="steps" value="three">
</li>
</ol>
<div class="flex flex-wrap items-center gap-4">
<button class="btn btn-primary" type="submit">Save order</button>
</div>
</form>
<script>
const form = document.getElementById('sortable-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const steps = new FormData(form).getAll('steps');
// Save steps, such as ['two', 'one', 'three'], in your application.
});
</script><form class="plf:flex plf:flex-col plf:gap-4" id="sortable-form">
<ol class="plf:sortable" data-plf-sortable aria-label="Steps">
<li class="plf:sortable-item" data-plf-value="one">
<button class="plf:sortable-handle" type="button" aria-label="Reorder Step one"></button>
<span class="plf:sortable-name">Step one</span>
<input type="hidden" name="steps" value="one">
</li>
<li class="plf:sortable-item" data-plf-value="two">
<button class="plf:sortable-handle" type="button" aria-label="Reorder Step two"></button>
<span class="plf:sortable-name">Step two</span>
<input type="hidden" name="steps" value="two">
</li>
<li class="plf:sortable-item" data-plf-value="three">
<button class="plf:sortable-handle" type="button" aria-label="Reorder Step three"></button>
<span class="plf:sortable-name">Step three</span>
<input type="hidden" name="steps" value="three">
</li>
</ol>
<div class="plf:flex plf:flex-wrap plf:items-center plf:gap-4">
<button class="plf:btn plf:btn-primary" type="submit">Save order</button>
</div>
</form>
<script>
const form = document.getElementById('sortable-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const steps = new FormData(form).getAll('steps');
// Save steps, such as ['two', 'one', 'three'], in your application.
});
</script>JavaScript
Sortable needs JavaScript and is an extension: the Full script and Full CSS leave it out. The Full script (pelatform-ui.min.js) and the extension scripts after it start every component on page load, with no code of your own; with Core and single files, the last line starts them. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<link rel="stylesheet" href="extensions/sortable.min.css">
<script src="pelatform-ui.min.js" defer></script>
<script src="extensions/sortable.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="extensions/sortable.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="extensions/sortable.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<link rel="stylesheet" href="extensions/sortable.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script>
<script src="extensions/sortable.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="extensions/sortable.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="extensions/sortable.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>Items are read once, so after adding or removing items call PLF.destroy(list), then PLF.init(list). With ESM imports, import Sortable from @pelatform/ui/extensions/sortable and call use(Sortable) before init().
Hooks
| Attribute | Purpose |
|---|---|
data-plf-sortable |
The named <ul> or <ol> |
data-plf-value |
Each item’s unique value, reported by getState and events |
data-plf-sortable-handle |
Optional; marks the handle in an item that holds more than one button. A disabled handle cannot start a move |
data-plf-sortable-name |
Optional; the text announcements use instead of the whole item |
data-plf-orientation |
vertical (default), horizontal (one row that scrolls sideways) or grid |
data-plf-instructions, data-plf-grabbed-message, data-plf-moved-message, data-plf-dropped-message, data-plf-cancelled-message |
On the list, translated messages, English by default; {item}, {position} and {count} are replaced |
The script adds hidden instructions to each handle’s aria-describedby and a role="status" region (data-plf-part="sortable-status") after the list.
Methods
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for valid markup |
PLF. |
Returns the instance or null |
instance. |
Returns {order, active}: the values in order, and the item being moved or null |
instance. |
Moves an item, or the item with that value, to a zero-based index. Returns false when canceled, unchanged, invalid or during another move |
instance. |
Cancels an active move and removes listeners, the instructions and the live region; the order stays |
| Event | When |
|---|---|
plf: |
After a drop or move; cancelable, which puts the item back |
plf: |
After the new order is kept |
Each event’s detail:
| Property | Value |
|---|---|
instance |
The Sortable instance |
item |
The moved item |
from |
The item’s zero-based index before the move |
to |
The item’s zero-based index after the move |
order |
The values in the new order |
reason |
pointer, keyboard or api |
trigger |
The item’s handle, or null from move |
| Key | Action |
|---|---|
| Space, Enter | On a handle, picks the item up; pressed again, drops it |
| Up Arrow, Down Arrow | Move the item one place, or one row in a grid |
| Left Arrow, Right Arrow | In a horizontal list or grid, move the item one place; reversed right to left |
| Home, End | Move the item to the first or last place |
| Escape, Tab | Cancel the move and return the item |
| Variable | Default |
|---|---|
--plf-sortable-gap |
--plf-space-2, 0. (8px) |
--plf-sortable-columns |
3, in a grid |
--plf-sortable-bg |
Surface |
--plf-sortable-border |
--plf-color-border-subtle |
--plf-sortable-radius |
--plf-radius-lg, 0. (8px) |
--plf-sortable-grabbed-bg |
--plf-color-primary-subtle, on an item picked up from the keyboard |