Skip to content

sidebar: Make every menu item a tab stop with a focus ring - #3119

Closed
huacnlee wants to merge 2 commits into
mainfrom
sidebar-menu-item-focus
Closed

huacnlee wants to merge 2 commits into
mainfrom
sidebar-menu-item-focus

Conversation

@huacnlee

Copy link
Copy Markdown
Member

Problem

SidebarMenuItem was a plain div + on_click; nothing in crates/component/src/sidebar/ called track_focus. GPUI's tab-stop table only lists nodes that painted with a tracked focus handle, so no sidebar item could be reached with Tab, and a keyboard user had no way to open a page.

Reported from a downstream application (ai-desktop/pilot, issue #230 there): the settings dialog's nine pages were unreachable, and the app rebuilt the item on gpui_kit::base::Button for that one reason.

Change

  • The row owns a keyed focus handle ((id, "focus")), the same pattern as Button. It is a tab stop by default and activates on Enter / Space through GPUI's keyboard click, so on_click receives ClickEvent::Keyboard.
  • Draws focus_ring_style while focused. A left mouse press calls prevent_default, as Button does, so the ring shows only during keyboard navigation.
  • Disabled items do not track focus and are skipped.
  • New builders: tab_stop(bool), tab_index(isize), and impl FocusableExt (focus_ring(false)) for containers that clip the ring and draw their own.
  • Exposes Role::Button and an aria_label from the item label.
  • Drops the row-level overflow_x_hidden: it would clip the outward ring, and the label containers inside already clip.

Tests

crates/component/src/sidebar/menu.rs:

  • tab_reaches_each_enabled_item_and_enter_activates_it — Tab walks Inbox → Sent → wraps, skipping the disabled item and the tab_stop(false) item; Enter fires each on_click.
  • space_activates_the_focused_item.

Both fail on main (no item is ever focused) and pass here.

cargo test -p gpui-component --lib sidebar      # 20 passed
cargo clippy -p gpui-component --all-targets -- --deny warnings
cargo fmt --check

Docs

website/component/sidebar.md and website/zh-CN/component/sidebar.md gain a "Keyboard Access / 键盘可达" section.

🤖 Generated with Claude Code

huacnlee and others added 2 commits September 18, 2026 18:13
`SidebarMenuItem` rendered a plain `div` with `on_click`, so nothing in
the sidebar subtree tracked focus: Tab could not reach a single item,
and a keyboard user had no way to open a page. A downstream app had to
rebuild the item on `base::Button` for that one reason.

The row now owns a keyed focus handle the way `Button` does. It is a
tab stop by default, draws the framework focus ring while focused, and
activates on Enter or Space through GPUI's keyboard click. A pointer
press does not move focus, so the ring only shows during keyboard
navigation. Disabled items stay out of the traversal. `tab_stop`,
`tab_index` and `FocusableExt::focus_ring` are exposed for the cases
that need to opt out. The row's `overflow_x_hidden` goes away because
it would clip the ring; the label containers already clip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant