Accessibility

Pointer drag, keyboard move-mode, and VoiceOver all call the same move-resolution function. This page is what that guarantees, concretely.

Why this matters

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:

KeyEffect
TabMove focus between cards.
Space / ReturnEnter move mode on the focused card (highlighted).
↑ / ↓Move the card up or down within its column.
← / →Move the card into the previous / next column.
ReturnCommit and exit move mode.
EscapeCancel — 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.

Give VoiceOver a real column name

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.