webui: [a11y] fix keyboard navigation issues in chat interface and sidebar#23132
Conversation
|
@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. |
|
hey @vignesh191, i will have it reviewed this week |
allozaur
left a comment
There was a problem hiding this comment.
Hey, @vignesh191 great job overall! Please just rebase and i think we good to go :)
|
@allozaur pulled and rebased, thanks for looking! |
|
@ServeurpersoCom please also review this and test on ur end :) |
ServeurpersoCom
left a comment
There was a problem hiding this comment.
Keyboard selection testing + quick non-reg test on latest Firefox / stock Edge / Chrome mobile. LGTM
* 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) ...
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
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 inActionIcon,BadgeInfo,ChatMessageStatistics,ChatMessageStatisticsBadge,ModelBadge,ModelsSelectorDropdown,DropdownMenuActions, and the fork-icon link inSidebarNavigationConversationItem. InChatMessageStatistics, the 4 view-switcher buttons were also extracted into a singleviewButtonsnippet to avoid duplicating the trigger pattern four timesopacity-0until you hovered, so tabbing to them did nothing visible. Added:focus-within / group-focus-within:opacity-100next to the existing hover rules inSidebarNavigationConversationItem,ChatAttachmentsListItemThumbnailFile, andChatAttachmentsListItemThumbnailImage.HorizontalScrollCarousel, the scroll arrows were hidden withopacity-0 pointer-events-nonewhen there was nothing to scroll, but they were still in the tab order. Now they usedisabledas the single source of truth, withdisabled:opacity-0 disabled:pointer-events-nonehandling the styling. This samedisabledpattern was used inChatScreenActionScrollDown.sveltetoo because it was causing a ghost focus whenShift+Tabbingfrom the initial chat<textarea>.mouseover, so keyboard focus had nothing to land on. Addedonfocusinto mount it when focused, andonfocusoutwith arelatedTarget.containscheck so it only unmounts once focus has actually left the row.canScrollLeft/canScrollRightonly updated on mount or scroll, so resizing the window or collapsing the sidebar wouldn't refresh them. Swapped thesetTimeoutfor aResizeObserveron the scroll container.HTMLButtonAttributesand added a...restspread so the child snippet pattern can forward trigger props through it. Needed for the tooltip fix to work onChatMessageStatisticsBadgeandModelBadge.Requirements