fix(sidebar): smooth collapse transitions, mobile rewrite, and new props#567
Conversation
commit: |
Docs PreviewCommit: |
- Replace Base UI Dialog mobile sidebar with plain aside + backdrop - Fix collapsible sections snapping closed on sidebar collapse - Add mobileBreakpoint and contentClassName props - Replace bg-kumo-tint with --sidebar-active-bg CSS variable - Remove inertValue helper; use imperative inert for React 18 compat - Update resize handle tests for button element
c4a76ee to
468f29e
Compare
…cus trap) - Add inert + aria-hidden on closed mobile sidebar - Add role=dialog and aria-modal when open - Add Escape key to close - Add focus management (move focus in on open, restore on close) - Update stale JSDoc comment (Dialog sheet -> modal sidebar sheet) - Add target prop to SidebarMenuButton for link targets
468f29e to
5524a1f
Compare
|
/bonk review from an a11y perspective please |
There was a problem hiding this comment.
a11y Review
What's working well ✅
- Escape-to-close, focus return on close, and
aria-modalon the mobile dialog are solid additions. - Keeping
aria-hidden+inerton inactiveSlidingViews preserves the expected accessibility mapping. aria-hidden="true"on the backdrop prevents AT from surfacing it as a stray interactive element.
Issues to address 🔧
-
Mobile sidebar lacks focus trap
Replacing Base UI Dialog removed the built-in focus trap. Since the mobile<aside>declaresrole="dialog"andaria-modal="true", keyboard users expect focus to be contained while it's open. Currently they can Tab out of the sidebar into the main content, which is not markedinert. This violates the ARIA dialog pattern and WCAG 2.4.3.Suggestion: Add a focus trap (e.g., via
react-focus-lockor a small custom hook that intercepts Tab/Shift+Tab at the edges) and mark the rest of the pageinertwhile the mobile sheet is open. -
Mobile dialog has no accessible name
The<aside role="dialog" aria-modal={openMobile}>does not havearia-labeloraria-labelledby, so screen readers announce a generic "dialog" without context.Suggestion: Add an accessible name:
role="dialog" aria-label="Navigation" aria-modal={openMobile} -
Collapsible content removed
inert
RemovinginertfromSidebarCollapsibleContentmeans focusable children inside closed sections are no longer explicitly removed from the tab sequence.aria-hidden+grid-rows-[0fr]don't guarantee that focusable descendants are unreachable in all browsers.The auto-expand
onFocushandler onSidebarCollapsiblepartially mitigates this, but a more robust pattern is to restoreinert(orvisibility: hidden) once the close animation finishes. Note that the existing test explicitly assertsinertis absent, so restoring it would need a test update too.
Overall direction is good — just need to close the focus-trap gap before this can ship from an a11y standpoint.
|
Submitted the a11y review on PR #567. Summary of findings:
The review is posted at: #567 (review) |
…ore inert on collapsible content
|
/bonk re-review. I think we've addressed the a11y issues |
a11y re-reviewThanks for addressing the previous feedback — the fixes are solid and the semantic shift from Dialog to ✅ What's working well
🔧 Remaining issues1. Focus return on Tab-out creates a ping-pong When users Tab forward past the last focusable item in the mobile sidebar, the Consider either:
2. Main content behind the backdrop is still reachable The mobile sidebar has a visual backdrop ( Since the Provider doesn't own the main-content sibling, consider documenting that consumers should wrap their main content and conditionally apply 3. No mobile test coverage The Consider adding a mobile-mode helper and at least a few mobile-specific tests (e.g. opening moves focus into the nav, Escape closes and returns focus, Overall direction is much improved. Closing the focus-steal gap and adding at least one mobile test would get this to ship-ready from an a11y standpoint. |
|
Posted the a11y re-review on PR #567: #567 (comment) Summary of the review:
|
|
/bonk re-review please |
There was a problem hiding this comment.
a11y re-review follow-up
✅ Issues addressed
1. Focus steal on Tab-out — resolved
The focusout handler now sets shouldRestoreFocusRef.current = false before closing the mobile drawer, and the focus-return useEffect only restores focus when that flag is true. The new test "should close on focus leave without stealing focus back" explicitly asserts that focus remains on the element outside the drawer. Clean implementation.
2. Mobile test coverage — resolved
Four mobile-specific tests were added covering:
- closed state:
inert+aria-hidden - open: focus moves to first focusable item
- Escape: closes drawer and returns focus to trigger
- focus leave: closes drawer without focus steal
🔧 Remaining minor item
Main content behind backdrop
useSidebar already exposes openMobile, so consumers can apply inert to their main content wrapper. Consider adding a brief note in the docs or JSDoc reminding consumers to do so — otherwise keyboard users may still Tab into obscured content despite the visual overlay.
Overall this is in good shape from an a11y standpoint.
|
Submitted the a11y re-review on PR #567. Summary:
Overall the PR is in good shape from an a11y standpoint. |

















































































Summary
Incremental improvements to the sidebar component on top of the v2.4.0 modernization.
New features
mobileBreakpointprop on Provider — configurable viewport width for mobile detectioncontentClassNameprop on Sidebar root — pass-through class for the inner content containeropenprop now controls the mobile sidebar too, not just desktopFixes
<aside>+ backdrop for simpler, more predictable transitionshiddenclass fromSidebar.MenuSubso sub-menus participate in collapse animationsinertValueReact-version helper —SidebarSlidingViewnow setsinertimperatively for React 18 compatibilityinertanddata-openattributes fromSidebarCollapsibleContentStyling
bg-kumo-tint→bg-(--sidebar-active-bg)CSS variable for active/hover/focus backgroundsIcon opacity
0.5→0.4; chevron gains hover opacity transitionHeader gains
shrink-0and animated padding on collapseContent scroll area gains animated
gaptransition andtabIndex={-1}on viewportSliding views container gains
max-w-(--sidebar-width)to prevent overflowMobile sidebar uses
--sidebar-animation-durationCSS variable for slide transitionReviews
bonk has reviewed the change
automated review not possible because: compound component with cross-cutting animation/a11y/layout concerns
Tests
Tests included/updated