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

  1. Vague action names like "More".
  2. A destructive action without confirmation or undo.
  3. Every rare action as a visible button.
  4. Replacing one hidden gesture with another hidden gesture.
  5. Duplicating system swipe actions with accessibilityAction.

WCAG mapping

  1. 2.5.1 Pointer Gestures: path and multipoint gestures have alternatives.
  2. 2.5.7 Dragging Movements: a drag has an alternative.
  3. 2.1.1 Keyboard: the action works without a pointer.
  1. Swipe Trap
  2. Tiny Target
  3. Platform Copy Paste
  1. Chapter 7
  2. Chapter 10
  3. Chapter 11
  4. Chapter 17
  1. VoiceOver gestures guide

Sources

  1. Apple Developer: accessibilityAction
  2. Apple Developer: UIAccessibilityCustomAction
  3. Apple Human Interface Guidelines: Gestures