Gesture Alternative
Status: ready
Intent
A gesture is a shortcut. It's never the only path to an action the task needs.
Problem
A swipe, drag, long press or pinch blocks users of VoiceOver, Switch Control, Voice Control, a keyboard, or anyone without fine motor control.
Solution
Verdict: the same action is also available as a visible control, a menu item, a keyboard command or an accessibility action. Choose by how often the action is used.
Why: a frequent action hidden in the Actions rotor is still hard to find. A rare action as a visible button on every row makes the screen a wall of controls. Frequency decides the trade-off.
| Action | Alternative |
|---|---|
| Primary (add to cart, pay, book) | Visible Button |
| One secondary row action | accessibilityAction, or system swipeActions |
| Several secondary actions | Visible Menu |
| Destructive | Menu or button, plus confirmation or undo |
| Map or chart zoom | Zoom in / Zoom out buttons |
| Choosing a location | A list (List Alternative) |
| Reordering | Edit mode, Move up / Move down actions, keyboard shortcuts |
Implementation
System swipe actions in a List are already in VoiceOver's Actions rotor. No duplicate accessibilityAction:
List(messages) { message in
MessageRow(message)
.swipeActions {
Button("Archive") { archive(message) }
}
}
A custom gesture needs the action added:
MessageRow(message)
.gesture(swipeToArchive(message))
.accessibilityAction(named: "Archive") { archive(message) }
Several actions, visible to everyone:
Menu {
Button("Share") { share(item) }
Button("Duplicate") { duplicate(item) }
Button("Delete", role: .destructive) { confirmDelete(item) }
} label: {
Label("More actions for \(item.name)", systemImage: "ellipsis.circle")
}
Pinch on a map:
HStack {
Button("Zoom out", systemImage: "minus") { zoomOut() }
Button("Zoom in", systemImage: "plus") { zoomIn() }
}
UIKit:
cell.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "Archive") { _ in
archive(message)
return true
}
]
Check
Profile: VoiceOver
Task: perform every row action
Expected: each gesture action is in the Actions rotor or a visible menu, once
destructive actions have confirmation or undo
Profile: Switch Control
Task: the same row actions
Expected: reachable without a swipe
cancel is possible
Profile: iPad with keyboard
Task: frequent actions
Expected: reachable with Tab, or with a ⌘ shortcut
Pitfalls
- Vague action names like "More".
- A destructive action without confirmation or undo.
- Every rare action as a visible button.
- Replacing one hidden gesture with another hidden gesture.
- Duplicating system swipe actions with
accessibilityAction.
WCAG mapping
2.5.1 Pointer Gestures: path and multipoint gestures have alternatives.2.5.7 Dragging Movements: a drag has an alternative.2.1.1 Keyboard: the action works without a pointer.