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
- Counting an iPad check as a Mac check.
- A swipe as the only action outside iPhone.
- A hidden focus ring.
- Hover as the only state cue.
- Assuming Catalyst gives Mac semantics on its own.
- Shortcuts that conflict with system ones (⌘Q, ⌘W, ⌘H).
WCAG mapping
2.1.1 Keyboard: tasks work from a keyboard.2.4.3 Focus Order: focus order follows the layout.2.4.7 Focus Visible: the focus indicator is visible.4.1.2 Name, Role, Value: controls have platform semantics.