Guide
Accessibility
Pointer drag, keyboard move-mode, and VoiceOver all call the same move-resolution function. This page is what that guarantees, concretely.
Most SwiftUI Kanban implementations are pointer-drag-only. A keyboard or VoiceOver user simply can't reorder a card. AdvancedKanban routes every input method through one shared function — KanbanMoveResolver.resolve — so a card moved by drag, by arrow key, or by a VoiceOver rotor action can never behave differently. This works out of the box; there's no accessibility mode to opt into.
Keyboard move-mode
Every card is focusable. No mouse required, start to finish:
| Key | Effect |
|---|---|
| Tab | Move focus between cards. |
| Space / Return | Enter move mode on the focused card (highlighted). |
| ↑ / ↓ | Move the card up or down within its column. |
| ← / → | Move the card into the previous / next column. |
| Return | Commit and exit move mode. |
| Escape | Cancel — the card returns to exactly where it started. |
Move mode's origin position is stashed the moment you enter it, so Escape is a real undo, not just "stop moving" — if you've already nudged the card three slots down and change your mind, Escape puts it back at slot zero.
VoiceOver
Each card exposes a value and a set of custom rotor actions:
// Announced on focus: "2 of 5 in In Progress" // Available actions (via the rotor): "Move Up" "Move Down" "Move to Backlog" "Move to Review" "Move to Done" // one per other column
"Move Up" and "Move Down" are real no-ops at the ends of a column — they don't wrap around or error, they just don't fire past the boundary. "Move to <Column>" is generated once per other column, so a card never gets an action to move into the column it's already in.
Without it, KanbanBoard falls back to describing a column by its raw id — readable if your id is a short string, unreadable if it's a UUID. Pass columnTitle in the initializer so "Move to <Column>" says something a person would actually say:
KanbanBoard( columns: $columns, columnTitle: { $0.name }, // "Move to In Progress", not "Move to column 5B2F..." // ... )
WIP limits are enforced identically everywhere
If a column's wipLimit would be exceeded and wipLimitBehavior is .preventDrop, the drop is rejected for pointer drag, keyboard move-mode, and VoiceOver alike — the limit isn't a pointer-only guardrail that a keyboard user could silently bypass.