A11y: Make resize handles for addon panel and sidebar accessible#33980
A11y: Make resize handles for addon panel and sidebar accessible#33980
Conversation
|
View your CI Pipeline Execution ↗ for commit b7c8a37
☁️ Nx Cloud last updated this comment at |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughLayout refactor: resizing logic moved into useDragging and dedicated SidebarContainer/PanelContainer components; Drag became an accessible, keyboard-aware separator; new size limits/constants and focusable UI landmarks were added; menu/shortcuts updated to restore focus to the sidebar region. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant DragHandle
participant LayoutState as useDragging
participant Sidebar as SidebarContainer
participant Panel as PanelContainer
User->>DragHandle: mouse drag or keyboard resize (arrow/Home/End)
DragHandle->>LayoutState: emit resize delta / keyboard intent
LayoutState->>LayoutState: compute clamp, sidebarMaxWidth, panelMaxSize
LayoutState->>Sidebar: update navSize / sidebarMaxWidth
LayoutState->>Panel: update panel size / panelMaxSize
Sidebar-->>User: updated width + aria updates
Panel-->>User: updated size/visibility + aria updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
📝 Coding Plan
Comment Tip Migrating from UI to YAML configuration.Use the |
|
@copilot rework this branch based on next. Some of the code for Drag was moved to its own file. Rework the code to remove conflicts and address PR feedback. |
Reworked in commit d1409a8. The branch now matches the
|
d1409a8 to
8048680
Compare
Sidnioulz
left a comment
There was a problem hiding this comment.
@copilot I've refactored to clean up a little. I am happy with the code but whilst doing QA I discovered an unhandled issue, so I'm adding a requirement to the PR.
I want you to look at how keyboard focus is handled for the addon panel in code/core/src/manager/components/layout/PanelContainer.tsx. shouldHidePanelContent is used to prevent focus into the panel when it is effectively hidden.
I want the same logic applied to the sidebar. Create a SidebarContainer file with the sidebar, drag handle and SidebarContainer. Make sure to hide the sidebar in the same way when its size is 0px to prevent incidents.
Then, I want you to apply the same focus logic as in the addon panel. When the sidebar is closed and focus was inside the sidebar (happens on a global kb shortcut), move nav to the "Show sidebar" button in the toolbar and fallback to document.body. When the "Show sidebar" button is pressed with kb, move focus to the sidebar region with the same animation and logic as for the addon panel.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@code/core/src/manager/components/layout/Drag.tsx`:
- Around line 104-111: The focus style for the separator (in the styled callback
using theme and $orientation, specifically the '&:focus-visible' rule) relies on
boxShadow which is suppressed in Windows High Contrast; add a forced-colors
fallback by nesting a `@media` (forced-colors: active) rule inside the same styled
object that removes the boxShadow fallback and instead sets a visible outline
(e.g., a solid/system color outline) so keyboard focus remains visible in
forced-colors mode while keeping the existing boxShadow behavior for normal
modes.
In `@code/core/src/manager/components/layout/PanelContainer.tsx`:
- Around line 43-53: Normalize the fallback for position once and use that
normalized value when computing overlapping, aria-valuenow, and aria-valuemax:
create a local const (e.g., normalizedPosition = position ?? 'bottom') at the
top of the PanelContainer render logic and replace direct uses of position in
the overlapping, aria-valuenow and aria-valuemax expressions with
normalizedPosition, keeping references to bottomPanelHeight and rightPanelWidth
for the bottom/right branches.
In `@code/core/src/manager/components/layout/useDragging.ts`:
- Around line 32-82: applyResizeKeyboard currently only clamps sizes to [0,
viewport], allowing keyboard-driven sizes to land between 0 and the component
minimums; update it to apply the same snap/open-collapse thresholds used in
onDragEnd so keyboard behavior matches mouse dragging. In applyResizeKeyboard,
after computing the clamped new size for sizeKey (navSize or rightPanelWidth or
bottomPanelHeight), apply the same snap logic used in onDragEnd: compare against
SIDEBAR_MIN_WIDTH_PX and RIGHT_PANEL_MIN_WIDTH_PX (and the bottom panel min if
applicable) and the same threshold used for collapsing/expanding (use the same
comparisons used in onDragEnd) so values below the threshold snap to 0
(collapsed) or to the min width/height; return the snapped size for the relevant
keys (navSize, rightPanelWidth, bottomPanelHeight) instead of the raw clamped
value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc9cceb7-fab9-4624-9649-4bbd1961cffa
📒 Files selected for processing (4)
code/core/src/manager/components/layout/Drag.tsxcode/core/src/manager/components/layout/Layout.tsxcode/core/src/manager/components/layout/PanelContainer.tsxcode/core/src/manager/components/layout/useDragging.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- code/core/src/manager/components/layout/Layout.tsx
Package BenchmarksCommit: The following packages have significant changes to their size or dependencies:
|
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 49 | 49 | 0 |
| Self size | 20.22 MB | 20.42 MB | 🚨 +206 KB 🚨 |
| Dependency size | 16.54 MB | 16.54 MB | 0 B |
| Bundle Size Analyzer | Link | Link |
@storybook/vue3-vite
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 108 | 108 | 0 |
| Self size | 36 KB | 36 KB | 🚨 +24 B 🚨 |
| Dependency size | 43.74 MB | 43.70 MB | 🎉 -37 KB 🎉 |
| Bundle Size Analyzer | Link | Link |
@storybook/cli
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 183 | 183 | 0 |
| Self size | 780 KB | 780 KB | 0 B |
| Dependency size | 67.40 MB | 67.61 MB | 🚨 +205 KB 🚨 |
| Bundle Size Analyzer | Link | Link |
@storybook/codemod
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 176 | 176 | 0 |
| Self size | 32 KB | 32 KB | 0 B |
| Dependency size | 65.93 MB | 66.14 MB | 🚨 +206 KB 🚨 |
| Bundle Size Analyzer | Link | Link |
create-storybook
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 50 | 50 | 0 |
| Self size | 1.04 MB | 1.04 MB | 🎉 -457 B 🎉 |
| Dependency size | 36.76 MB | 36.96 MB | 🚨 +206 KB 🚨 |
| Bundle Size Analyzer | node | node |
Implemented in commit 6cd1968: SidebarContainer.tsx — new component mirroring PanelContainer:
Focus management on close (shortcuts.ts
Focus management on open (menu.tsx):
focusableUIElements — added |
Good call, it's an oversight. I've added individual prop tests for the Drag component, and interaction tests for the overall kb interactions we use in Layout. |
Fixed! The keyboard resizing will now also collapse when reaching minimum dimensions. I've also set a minimum dimension of 40px for the bottom panel, as it's really the minimum we need to see to use it.
I could not reproduce any such bug. Could you please tell me more? |
JReinhold
left a comment
There was a problem hiding this comment.
The keyboard resizing will now also collapse when reaching minimum dimensions
Works great and works with the fullscreen button states still. Great job!
- Add role="separator", tabIndex, aria-orientation, aria-label, aria-valuenow, aria-valuemin, aria-valuemax to sidebar and panel drag handles - Add keyboard interaction (arrow keys, Home/End) for resizing via keyboard - Add focus-visible styles so drag handles are visible when focused Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
- Refactor keyboard handlers into shared applyResizeKeyboard function with position parameter (left/right/bottom/top) - Add Shift key support for 5x step size - Move slotSidebar before Drag handle in sidebar container for better keyboard navigation order - Update focus-visible styles with outline:none, height:7, and boxShadow for the drag handle Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
- Extract Drag.tsx component with built-in role=separator, tabIndex, aria-valuemin, and TooltipProvider (focus-only) showing arrow keys - Extract PanelContainer.tsx with ARIA attributes and hidden/aria-hidden for collapsed panel content - Extract MainAreaContainer.tsx matching next branch structure - Simplify Layout.tsx to use extracted components, reducing markup - Fix focus-visible: use height when horizontal, width when vertical - Sidebar slot stays before Drag handle for keyboard nav order Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
4802508 to
71afd18
Compare
…ggle - Create SidebarContainer.tsx with hidden/aria-hidden when navSize === 0 - Add sidebarRegion and showSidebar IDs to focusableUIElements - Update menu.tsx tool with focus management and landmark animation - Update shortcuts.ts to move focus to Show sidebar button when closing - Simplify Layout.tsx to use SidebarContainer component Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
…e max size for aria-valuemax - Drag.tsx: Use `outline: 2px solid transparent` with `@media (forced-colors: active)` fallback to `Highlight` so focus is visible in Windows High Contrast mode - PanelContainer.tsx: Normalize `position ?? 'bottom'` once as `resolvedPosition` to avoid inconsistent defaults - SidebarContainer.tsx/PanelContainer.tsx/useDragging.ts: Compute effective max size accounting for CSS grid's MINIMUM_CONTENT_WIDTH_PX constraint and space occupied by other regions, so aria-valuemax reflects the actual visual limit and keyboard resizing stays responsive - Move MINIMUM_CONTENT_WIDTH_PX to shared constants file to avoid circular imports Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
Co-authored-by: Jeppe Reinhold <jeppe@chromatic.com>
Testing
UI Review
https://www.chromatic.com/review?appId=635781f3500dd2c49e189caf&number=33980&type=linked
Manual testing
Copilot stream of consciousness
Resize handles for the sidebar and addon panel lack semantic ARIA attributes and keyboard interaction, making them invisible to assistive technologies per the WAI-ARIA separator role spec.
Drag.tsxcomponentExtracted
Draginto its own React component (Drag.tsx) that automatically setsrole="separator",tabIndex={0}, andaria-valuemin={0}, reducing ARIA boilerplate at call sites. Callers only need to provide:aria-orientation—verticalfor sidebar/right panel,horizontalfor bottom panelaria-label— "Sidebar resize handle" / "Addon panel resize handle"aria-valuenow/aria-valuemax— current size in px, max viewport dimensionA
TooltipProviderwithtriggerOnFocusOnlywraps the handle, showing arrow key hints on focus (← → to resizefor vertical,↑ ↓ to resizefor horizontal). The tooltip usesTooltipNotefor consistent formatting and skips injectingaria-describedby(redundant with the separator role and label).Focus-visible styles apply
outline: 2px solid transparentwithboxShadow: inset 0 0 0 4px ${theme.color.secondary}, and conditionallyheight: 7when horizontal orwidth: 7when vertical. A@media (forced-colors: active)fallback rendersoutline: 2px solid Highlightso the focus indicator remains visible in Windows High Contrast mode.SidebarContainer.tsxExtracted
SidebarContainerinto its own file, mirroringPanelContainer. When the sidebar is collapsed (navSize === 0), sidebar content is hidden from keyboard and assistive technologies viahiddenandaria-hidden. The drag handle is always rendered outside the hidden slot so users can reopen the sidebar.PanelContainer.tsxandMainAreaContainer.tsxExtracted
PanelContainerandMainAreaContainerinto separate files to match thenextbranch structure.PanelContainerencapsulates the addon panel'sDraghandle with all ARIA attributes and hides collapsed panel content from assistive technologies viahiddenandaria-hidden. Thepositionprop is normalized once asresolvedPositionto ensure consistent fallback behavior across all position-dependent logic.Effective
aria-valuemaxand keyboard resize limitsThe CSS grid reserves
MINIMUM_CONTENT_WIDTH_PX(100px) for the content column, so using rawwindow.innerWidth/window.innerHeightoverstates the actual available space. Both containers anduseDragging.tsnow compute effective maximum sizes that account for the grid constraint and the space occupied by other layout regions:window.innerWidth - 100 - rightPanelWidth(when panel is right)window.innerWidth - 100 - navSizewindow.innerHeightMINIMUM_CONTENT_WIDTH_PXis defined in a sharedconstants.tsfile to avoid circular imports.Keyboard interaction (
useDragging.ts)applyResizeKeyboardhelper with apositionparameter (left/right/bottom/top) per the Window Splitter APG patternleft):ArrowRightincreases /ArrowLeftdecreasesbottom):ArrowUpincreases /ArrowDowndecreasesright):ArrowLeftincreases /ArrowRightdecreasesHome→ minimum size (0),End→ maximum size (effective viewport dimension)Focus management on sidebar/panel toggle
When the sidebar or addon panel is closed via a global keyboard shortcut and focus was inside the region, focus moves to the corresponding "Show sidebar" / "Show addon panel" toolbar button (with fallback to
document.body). When either toolbar button is activated via keyboard (Enter/Space), the region opens, focus is polled into the region element, and a landmark animation highlights the target area.sidebarRegionandshowSidebartofocusableUIElementsinlayout.tsmenu.tsxtoolbar button with ID,focusOnUIElementpolling, anduseRegionFocusAnimation(matching the existingaddons.tsxpattern)shortcuts.tstoggleNavhandler with focus rescue logic (matching the existingtogglePanelpattern)Layout changes
SidebarContainer,PanelContainer, andMainAreaContainercomponentsOriginal prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.
Summary by CodeRabbit
New Features
Accessibility
Bug Fixes / UI