Skip to content

webui: [a11y] fix keyboard navigation issues in chat interface and sidebar#23132

Merged
ServeurpersoCom merged 13 commits into
ggml-org:masterfrom
vignesh191:landingPageKeyboardNav
Jun 4, 2026
Merged

webui: [a11y] fix keyboard navigation issues in chat interface and sidebar#23132
ServeurpersoCom merged 13 commits into
ggml-org:masterfrom
vignesh191:landingPageKeyboardNav

Conversation

@vignesh191
Copy link
Copy Markdown
Contributor

@vignesh191 vignesh191 commented May 16, 2026

Overview

This PR fixes a bunch of keyboard navigation bugs in the chat UI and sidebar (see details below).

The main issues: some elements had two tab stops when they should've had one, some focusable elements were invisible until you hovered them (so keyboard users couldn't see where they were making them inaccessible), and the kebab menu on sidebar conversations wasn't reachable by keyboard at all.

Affects action icons, model and stat badges, attachment thumbnails, the horizontal scroll carousel, the model selector dropdown, and the conversation sidebar.

This addresses issues described by #13531 and #20832

Demo demonstrating keyboard navigation (I'm Tabbing and Shift+Tabbing around), this can also be tested using the local Storybook:

shiftdemoing.mov

Additional information

  • Double tab stops from Tooltip.Trigger: <Tooltip.Trigger> renders its own <button> by default, so wrapping it around something that's already a button (or link, or another trigger) gave you two tab stops and nested <button> elements, which is invalid HTML. Fixed by using bits-ui's child snippet, which is the same fix applied in webui : [ChatFormActionAdd][a11y] fix accessibility issues in add menu trigger and items #22736. Applied in ActionIcon, BadgeInfo, ChatMessageStatistics, ChatMessageStatisticsBadge, ModelBadge, ModelsSelectorDropdown, DropdownMenuActions, and the fork-icon link in SidebarNavigationConversationItem. In ChatMessageStatistics, the 4 view-switcher buttons were also extracted into a single viewButton snippet to avoid duplicating the trigger pattern four times
  • Hover-only controls now show on focus too: the sidebar conversation kebab and the X button on attachment thumbnails were opacity-0 until you hovered, so tabbing to them did nothing visible. Added :focus-within / group-focus-within:opacity-100 next to the existing hover rules in SidebarNavigationConversationItem, ChatAttachmentsListItemThumbnailFile, and ChatAttachmentsListItemThumbnailImage.
  • Carousel arrows no longer focusable when hidden: in HorizontalScrollCarousel, the scroll arrows were hidden with opacity-0 pointer-events-none when there was nothing to scroll, but they were still in the tab order. Now they use disabled as the single source of truth, with disabled:opacity-0 disabled:pointer-events-none handling the styling. This same disabled pattern was used in ChatScreenActionScrollDown.svelte too because it was causing a ghost focus when Shift+Tabbing from the initial chat <textarea>.
  • Conversation kebab now mounts on keyboard focus: the kebab only rendered on mouseover, so keyboard focus had nothing to land on. Added onfocusin to mount it when focused, and onfocusout with a relatedTarget.contains check so it only unmounts once focus has actually left the row.
  • Carousel arrows now respond to size changes: canScrollLeft / canScrollRight only updated on mount or scroll, so resizing the window or collapsing the sidebar wouldn't refresh them. Swapped the setTimeout for a ResizeObserver on the scroll container.
  • BadgeInfo API tweak: extended its props with HTMLButtonAttributes and added a ...rest spread so the child snippet pattern can forward trigger props through it. Needed for the tooltip fix to work on ChatMessageStatisticsBadge and ModelBadge.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: Yes, AI was used to the UI component libraries patterns/usage and actually make the code diff, after a viable solution for each were decided upon

@vignesh191 vignesh191 changed the title [WIP] use child snippets for landing and chat message elements webui: [a11y] fix keyboard navigation issues in chat interface and sidebar in webui May 24, 2026
@vignesh191 vignesh191 changed the title webui: [a11y] fix keyboard navigation issues in chat interface and sidebar in webui webui: [a11y] fix keyboard navigation issues in chat interface and sidebar May 24, 2026
Comment thread tools/ui/src/lib/components/app/badges/BadgeInfo.svelte
Comment thread tools/ui/src/lib/constants/image-size.ts
Comment thread tools/ui/src/lib/utils/cap-img-size.ts
@vignesh191 vignesh191 marked this pull request as ready for review May 25, 2026 22:17
@vignesh191 vignesh191 requested a review from a team as a code owner May 25, 2026 22:17
@vignesh191
Copy link
Copy Markdown
Contributor Author

@allozaur I would appreciate it greatly if you can take a look at this PR when you can 😄. This PR makes keyboard navigation from/to the chat input and chat history sidebar to work correctly. I've detailed the changes in the PR description and have left comments throughout the PR to clarify less obvious changes.

Do note that I didn't open an issue for this since it's a collection of small cleanups/fixes. Splitting them into separate issues/PRs felt heavier than the changes warranted imho.

@allozaur
Copy link
Copy Markdown
Contributor

hey @vignesh191, i will have it reviewed this week

Copy link
Copy Markdown
Contributor

@allozaur allozaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @vignesh191 great job overall! Please just rebase and i think we good to go :)

@vignesh191
Copy link
Copy Markdown
Contributor Author

@allozaur pulled and rebased, thanks for looking!

@allozaur allozaur requested a review from ServeurpersoCom June 4, 2026 11:00
@allozaur
Copy link
Copy Markdown
Contributor

allozaur commented Jun 4, 2026

@ServeurpersoCom please also review this and test on ur end :)

Copy link
Copy Markdown
Contributor

@ServeurpersoCom ServeurpersoCom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyboard selection testing + quick non-reg test on latest Firefox / stock Edge / Chrome mobile. LGTM

@ServeurpersoCom ServeurpersoCom merged commit 42b2d60 into ggml-org:master Jun 4, 2026
6 checks passed
gabe-l-hart added a commit to gabe-l-hart/llama.cpp that referenced this pull request Jun 4, 2026
* origin/master: (57 commits)
server : disable on-device spec checkpoints (ggml-org#24108)
arg: fix double mtp downloads (ggml-org#24128)
webui: [a11y] fix keyboard navigation issues in chat interface and sidebar (ggml-org#23132)
Move duplicated imatrix code into single common imatrix-loader.cpp (ggml-org#22445)
ui: Fixed packages (ggml-org#24119)
ui: added single line reasoning preview (ggml-org#23601)
return filter to save memory (ggml-org#24125)
convert: Fix Gemma 4 Unified conversion (ggml-org#24118)
ggml: vectorize ggml_vec_dot_q4_1_q8_1 with WASM SIMD128 (ggml-org#22209)
server: avoid unnecessary checkpoint restore when new tokens are present (ggml-org#24110)
agents: refactor, include more guidelines (ggml-org#24111)
webui: fix tool selector toggle/counter, key tools by stable identity (ggml-org#24065)
build : use umbrella Headers directory for XCFramework module map (ggml-org#23974)
server : add header to tools/server/server-http.h (ggml-org#24089)
cmake: skip cvector-generator and export-lora when CPU backend is disabled (ggml-org#24053)
fix(mtmd): handle Gemma 4 audio projector embedding size (ggml-org#24091)
readme : add status badges (ggml-org#24104)
tests : refactor test-save-load-state to accept token input (ggml-org#24073)
metal : reduce rset heartbeat from 500ms -> 5ms (ggml-org#24074)
ggml-webgpu: FlashAttention refactor + standardize quantization support (ggml-org#23834)
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants