Skip to content

Keyboard, focus, and accessibility: make the whole app mouse-free and HIG-correct #1490

@datlechin

Description

@datlechin

Problem

TablePro has the focus-management problem common to native macOS apps: keyboard focus and tab order break at the handoffs between views. You can start a task on the keyboard, but somewhere along the way focus lands nowhere (or on the wrong control), and you have to reach for the mouse. This hurts keyboard-first users, power users, and anyone relying on VoiceOver or Full Keyboard Access.

This is not a single-screen bug. It is a whole-app quality bar: every interactive surface should be fully operable by keyboard, with a correct responder chain, a sensible tab order, visible focus, and proper accessibility, following Apple's HIG and AppKit/SwiftUI conventions. No mouse should ever be required to complete a normal task.

Reported by @vimtor on X, who also gave the canonical test flow below.

Reference flow (the acceptance test)

Complete this end to end without touching the mouse:

  1. Open a table.
  2. Move through cells (arrows / Tab).
  3. Filter by a column.
  4. Change the filter column and value.
  5. Apply the filters.
  6. Move through cells again.

Today focus drops or misroutes at several of these handoffs (entering the filter UI, moving between filter column and value, applying, and returning to the grid). That round trip is the bar: focus must flow grid to filter and back with no dead ends.

Scope (app-wide, not just the flow above)

Audit and fix keyboard, focus, and accessibility across every surface:

  • Data grid: arrow/Tab/Shift-Tab cell navigation, Home/End/PageUp/PageDown, Enter to edit, Esc to cancel, Return to commit, type-to-edit, copy/paste selection, and focus returning to the right cell after edit or after a sheet/popover closes.
  • Filter UI: open by keyboard, Tab order across column / operator / value, add and remove filter rows, apply (Return) and clear, and focus returning to the grid afterward.
  • Sidebar / database tree: arrow navigation, expand/collapse, type-select, and Tab into and out of the tree.
  • Tabs (native NSWindow tabs): Cmd-number, next/previous tab, and focus landing in the tab content after switching.
  • Query editor: focus in/out, and that editor shortcuts do not trap focus.
  • Right sidebar / inspector and cell editors: Tab order through fields, commit/cancel, and focus restoration on close.
  • Connection form and welcome screen: full Tab order, default button (Return) and Cancel (Esc).
  • All sheets, popovers, alerts, dialogs: initial focus on the right control, default and cancel actions, Esc to dismiss, and focus restored to the opener on close.
  • Menus and global shortcuts: every primary action reachable from the menu bar with a discoverable shortcut.

Correct approach (non-negotiable)

Do this the platform's way. No hacks, no custom focus engines, no reinventing controls.

  • Use the native AppKit key view loop and NSResponder chain (nextKeyView, acceptsFirstResponder, becomeFirstResponder, recalculateKeyViewLoop) for AppKit-backed views, and SwiftUI's own focus system (@FocusState, .focusable, .focusSection, .focusScope, .defaultFocus, .focused) for SwiftUI views. Bridge the two correctly at NSViewRepresentable boundaries instead of forcing focus by hand.
  • Default and cancel actions via .keyboardShortcut(.defaultAction) / .cancelAction and keyEquivalent "\r" / Esc, not manual key interception.
  • Honor macOS Full Keyboard Access (System Settings) and draw the standard focus ring; do not suppress it.
  • Accessibility: every control has an accessibility label/role/value, custom views included, so VoiceOver announces and navigates them.
  • Match HIG for focus order (top-to-bottom, leading-to-trailing), initial focus, and focus restoration after modal dismissal.

If a native API already provides the behavior, use it. A hand-rolled approximation that passes a click test but mishandles Full Keyboard Access, VoiceOver, the responder chain, or focus restoration is not acceptable.

Acceptance criteria

  • The reference flow above works mouse-free, both directions.
  • Every surface in the scope list is fully keyboard-operable with a correct Tab order.
  • Focus is always visible (standard focus ring), never lost to nowhere.
  • VoiceOver can reach and operate every control.
  • Full Keyboard Access mode works throughout.
  • No custom focus hacks; fixes use documented AppKit/SwiftUI APIs.

Alternatives considered

Fixing only the reported filter flow. Rejected: focus breakage is systemic, so a per-screen patch would leave the same class of bug everywhere else.

Related database type

N/A / General

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions