Kanban
elements man ui/templates/kanbanA board of columns of cards you reorder and move by pointer, touch or keyboard, with announcements and a one-column phone layout.
Exampleimport { Kanban, KanbanBoard, KanbanColumn, KanbanItem, KanbanItemMove, KanbanNav, KanbanNavItem, } from "@elements/ui/kanban"; <Kanban value={state.columns} label="Sprint board"> <KanbanNav> <KanbanNavItem e:for={id of Object.keys(state.columns)} e:key={(c) => c} value={id}>{TITLES[id]}</KanbanNavItem> </KanbanNav> <KanbanBoard> <KanbanColumn e:for={id of Object.keys(state.columns)} e:key={(c) => c} value={id} title={TITLES[id]} count={state.columns[id].length}> <KanbanItem e:for={task of state.columns[id]} e:key={(t) => t.id} value={task.id}> {task.title} <KanbanItemMove /> </KanbanItem> </KanbanColumn> </KanbanBoard> </Kanban>
Parts
| Part | What it is |
|---|---|
Kanban |
The root. Owns value (two-way) and every drag; renders the drag overlay in the top layer. |
KanbanBoard |
The row of columns (a stack when vertical). Scrolls on its main axis; snaps one column at a time on a phone. |
KanbanColumn |
One column: a header from title and count, the card list (scrolls on its own), and header, actions and footer slots. |
KanbanColumnHandle |
A grip button that drags its column. Columns reorder only with a handle or asHandle. |
KanbanItem |
One card. It drags from a KanbanItemHandle, or as a whole card with asHandle; with neither it stays put. |
KanbanItemHandle |
A grip button that drags its card; the rest of the card stays clickable and selectable. |
KanbanItemMove |
Previous and next column buttons, the tap control every draggable needs on a phone. Shown in the compact layout by default. |
KanbanNav |
A row of column buttons above the board on a phone: each scrolls to its column, and the one in view is current. |
KanbanNavItem |
One column's button in KanbanNav. |
.kanban-drop / .kanban-board-drop |
The placeholder that marks the slot while a card or column is over it. |
.kanban-overlay |
The dragged copy, in the top layer, so a scrolling column never clips it. |
Attributes
Kanban
| Attribute | Type | Default | Description |
|---|---|---|---|
class |
string |
"" |
Extra classes on the part's root. |
Kanban
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
Record<string, any[]> (two-way) |
{} |
Column id to its items; key order is column order. Written once per move, never during a drag. |
getItemValue |
(item: any) => string | number |
item.id, else the item |
The id of an item. Must match its KanbanItem value. |
orientation |
"horizontal" | "vertical" |
"horizontal" |
Columns side by side, or stacked. |
label |
string |
"" |
The board's accessible name. |
dragging |
{ kind: "item" | "column"; id: string } | null (two-way) |
null |
What is being dragged. Bind it to render a custom overlay. |
disabled |
boolean |
false |
Nothing drags and move buttons are disabled. |
autoScroll |
boolean |
true |
Scroll the board, the column and the page when a dragged card nears their edges. |
restrictTo |
"none" | "vertical" | "horizontal" | "board" | "window" |
"none" |
Lock the dragged copy to an axis, or keep it inside the board or the window. |
keyboardCodes |
{ start?: string[]; cancel?: string[]; end?: string[] } |
Space, Enter; Escape; Space, Enter, Tab |
The KeyboardEvent.code values that pick up, cancel and drop. Change instructions to name them. |
flatCursor |
boolean |
false |
Default cursor instead of grab and grabbing. |
announcements |
{ start?, over?, end?, cancel? } |
{} |
Replace any screen reader message. Each gets { kind, label, column, position, total }; positions start at 1. |
instructions |
string |
pick up, move, drop, cancel |
Screen reader instructions wired to every activator with aria-describedby. |
roleDescription |
string |
"sortable" |
aria-roledescription on every activator; translate it with instructions and announcements. |
onValueChange |
(value) => void |
() => {} |
After the board wrote a new value. |
onMove |
(e: KanbanMoveEvent) => void |
() => {} |
Before a move is written: kind, id, from, to, value. e.preventDefault() keeps value (a limit, or a server that decides). |
onDragStart |
(e: KanbanDragEvent) => void |
() => {} |
A card or column was picked up. |
onDragOver |
(e: KanbanDragEvent) => void |
() => {} |
The slot under the drag changed (e.over). |
onDragMove |
(e: KanbanDragEvent) => void |
() => {} |
Every pointer move and keyboard step while dragging. |
onDragEnd |
(e: KanbanDragEvent) => void |
() => {} |
Dropped, moved or not. |
onDragCancel |
(e: KanbanDragEvent) => void |
() => {} |
Escape, a lost pointer, blur, or a drop onMove refused cancelled the drag. |
Kanban, KanbanColumn
| Attribute | Type | Default | Description |
|---|---|---|---|
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Other attributes for the root or the column: id, data-testid. |
KanbanColumn
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string |
(required) | The column's key in value. |
title |
string |
"" |
The header title; names the column. |
count |
string | number |
"" |
A count tag beside the title. |
label |
string |
"" |
The column's name when it has no title. |
disabled |
boolean |
false |
Cannot be dragged and takes no cards. |
asHandle |
boolean |
false |
A press anywhere on the column (not on a card or a control) drags it; its grab is the keyboard button. |
KanbanItem
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | number |
(required) | The item's id. |
label |
string |
its text |
The name announcements use. |
disabled |
boolean |
false |
Cannot be dragged or moved; others still move around it. |
asHandle |
boolean |
false |
The whole card drags, even with a KanbanItemHandle inside. Without it or a handle the card does not drag. |
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Other attributes for the card: id, data-testid. |
KanbanItemHandle, KanbanColumnHandle
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string |
"Drag card" / "Drag column" |
The handle's accessible name. |
disabled |
boolean |
false |
The handle does not drag; its card or column stays enabled. |
KanbanItemMove
| Attribute | Type | Default | Description |
|---|---|---|---|
show |
"compact" | "always" |
"compact" |
Show the move buttons only in the one-column layout, or always. |
labels |
{ to?, previous?, next?, noPrevious?, noNext? } |
"Move to {column}", "No previous column", "No next column" |
The buttons' accessible names, for translation. {column} in to is the target column's name. |
KanbanNav
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string |
"Columns" |
The nav's accessible name. |
always |
boolean |
false |
Show at every width, not only in the one-column layout. |
KanbanNavItem
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string |
(required) | The column it scrolls to. |
KanbanItemHandle, KanbanColumnHandle, KanbanNavItem
| Attribute | Type | Default | Description |
|---|---|---|---|
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Other attributes for the button: id, data-testid, title. |
Slots
| Slot | Part | Content |
|---|---|---|
default |
Kanban |
KanbanNav and KanbanBoard. |
overlay |
Kanban |
Custom drag overlay content; replaces the copy of the dragged element while it has content. |
default |
KanbanColumn |
The cards. |
actions |
KanbanColumn |
The end of the header: a handle, a menu. |
header |
KanbanColumn |
A header of your own, for a column without title. |
footer |
KanbanColumn |
Below the cards: an add button. |
default |
KanbanItem |
Card content. |
icon |
KanbanItemHandle, KanbanColumnHandle |
The grip; a grip icon by default. |
Keyboard
| Keys | Action |
|---|---|
Space / Enter |
Pick up the focused card or column; press again to drop it. |
ArrowUp / ArrowDown |
Move the card one slot in its column. On a vertical board, past the end into the next column. Move a column on a vertical board. |
ArrowLeft / ArrowRight |
Move the card to the previous or next column at the nearest slot (flipped right to left). Move a column. |
Home / End |
First or last slot. |
Escape |
Cancel; the card returns. |
Tab |
Next card or control. While lifted, drops and keeps focus on the card. |
ArrowLeft / ArrowRight (nav) |
Move between column buttons. |
Data attributes
| Attribute | Values | On | Description |
|---|---|---|---|
data-orientation |
"horizontal" | "vertical" |
root, board | |
data-dragging |
"item" | "column" |
root | While a drag runs. |
data-dragging |
present | card, column, activator | On the one being dragged. |
data-over |
present | column | The column a card would drop into. |
data-disabled |
present | root, column, card, handle | |
data-kanban-activator |
present | card, handle, grab | What takes the keys that start a drag. |
data-kanban-surface |
present | card, column | Its grab is the button; a press on it drags. |
data-active |
present | .kanban-drop, .kanban-board-drop | While it marks the slot. |
data-source |
"pointer" | "keyboard" |
overlay | |
data-state |
"dragging" | "dropping" |
overlay | |
aria-current |
"true" |
nav item | The column in view. |
Accessibility
- The board is a group named by label; each column is a group named by its title, and its cards are a list: each card sits in a listitem.
- Every activator is a button (a handle, or role="button" on an asHandle card, so every draggable has one) and a tab stop with aria-roledescription="sortable" (roleDescription) and instructions wired by aria-describedby; it reports aria-pressed="true" while lifted and aria-disabled when it cannot move. A handle's aria-controls names the card or column it drags.
- No control sits inside a role="button": an asHandle card that holds controls, and an asHandle column, get a grab instead, an empty button laid over them, named for them, which takes the keys and the focus ring while presses still drag the card or column. A grip inside an asHandle card or column is inert.
- Pick up, every slot change, drop and cancel are announced in a live region with positions that start at 1 and the column's name.
- Focus stays on the card during a keyboard drag and follows it into its new column on drop.
- Every drag has a tap equivalent: KanbanItemMove's buttons are named for the column they move to and disabled at the ends.