Platform Interaction Contract

Status: ready

Intent

One user task stays available on iPhone, iPad, Mac and Catalyst, and each platform reaches it with its own input.

Problem

Shared UI keeps touch assumptions. On iPad and Mac there's no keyboard path, no menu command, no focus ring and no table semantics.

Solution

Verdict: each important component and flow has a short written contract: what the task is, and how each platform exposes it.

Why: the contract gives design, development and QA one target per platform. Without it, the Mac version inherits iPhone gestures by default.

TransactionRow
Meaning:  one transaction: merchant, amount, date, status
iPhone:   tap for details, swipe shortcut, VoiceOver custom actions
iPad:     Tab focus, context menu, pointer, ⌘ shortcuts
Mac:      Table row, menu command, ⌘ shortcut, column headers

Implementation

Menu commands on the scene, with the selected item from @FocusedValue:

WindowGroup { ContentView() }
    .commands { TransactionCommands() }

struct TransactionCommands: Commands {
    @FocusedValue(\.selectedTransaction) private var transaction

    var body: some Commands {
        CommandMenu("Transactions") {
            Button("Export Selected Transaction") { transaction?.export() }
                .keyboardShortcut("e")
                .disabled(transaction == nil)
        }
    }
}

The same action keeps one concept across surfaces:

iPhone button:      Export transaction
iPad context menu:  Export transaction
Mac menu item:      Export Selected Transaction

Focus and selection are different things. Selection is data. Focus is where the user is now:

After closing the details sheet:
  focus returns to the transaction row
  the same transaction stays selected

Per-platform checklist before a shared component is done:

iPhone:  VoiceOver order, custom actions for swipes, Larger Text, Reduce Motion
iPad:    Tab focus path, ⌘ shortcuts, pointer state not only color, Split View
Mac:     menu commands, visible focus ring, Table semantics, VoiceOver on Mac

Check

Profile:   iPad, keyboard, Full Keyboard Access
Task:      export a transaction
Expected:  Tab reaches the row and the action
           ⌘E works
           narrow Split View doesn't hide the action
Profile:   Mac, VoiceOver
Task:      export the selected transaction
Expected:  rows read merchant, amount and status
           the menu command is in the menu bar
           focus and selection are unchanged after the export

Pitfalls

  1. Counting an iPad check as a Mac check.
  2. A swipe as the only action outside iPhone.
  3. A hidden focus ring.
  4. Hover as the only state cue.
  5. Assuming Catalyst gives Mac semantics on its own.
  6. Shortcuts that conflict with system ones (⌘Q, ⌘W, ⌘H).

WCAG mapping

  1. 2.1.1 Keyboard: tasks work from a keyboard.
  2. 2.4.3 Focus Order: focus order follows the layout.
  3. 2.4.7 Focus Visible: the focus indicator is visible.
  4. 4.1.2 Name, Role, Value: controls have platform semantics.
  1. Platform Copy Paste
  2. Swipe Trap
  1. Chapter 12
  2. Chapter 16
  3. Chapter 18

Sources

  1. Apple Human Interface Guidelines: Keyboards
  2. Apple Human Interface Guidelines: Menus
  3. Apple Human Interface Guidelines: Focus and selection
  4. Apple Developer: Commands
  5. Apple Developer: FocusedValue