fix(Header/DashboardSidebar/Sidebar): allow auto focus in menu for proper focus trapping#6266
Conversation
Remove the onOpenAutoFocus preventDefault handler from menuProps
that was blocking reka-ui's Dialog auto-focus behavior.
When the header modal opens via keyboard, focus should move inside
the modal for proper focus trapping. The previous handler prevented
this, causing Tab navigation to traverse background page content
before the focus trap engaged, violating WCAG 2.4.3 (Focus Order).
Users who need the old behavior can opt in via the menu prop:
<UHeader :menu="{ content: { onOpenAutoFocus: (e) => e.preventDefault() } }" />
Resolves nuxt#6257
📝 WalkthroughWalkthroughThis change removes the Estimated code review effort🎯 1 (Trivial) | ⏱️ ~4 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/components/Header.spec.ts (1)
44-55: Current assertion doesn’t verify the auto-focus contract.Line 54 only checks render truthiness; it won’t catch regressions in focus movement/focus trap behavior.
Suggested test strengthening
+import { nextTick } from 'vue' import { describe, it, expect } from 'vitest' import { axe } from 'vitest-axe' import { mountSuspended } from '@nuxt/test-utils/runtime' @@ - it('does not suppress auto-focus in modal menu props', async () => { + it('moves focus inside the modal when opened', async () => { const wrapper = await mountSuspended(Header, { props: { open: true, mode: 'modal', menu: { portal: false } } }) - // Verify the modal content is rendered when open - expect(wrapper.html()).toBeTruthy() + await nextTick() + const dialog = wrapper.find('[role="dialog"]') + expect(dialog.exists()).toBe(true) + expect(dialog.element.contains(document.activeElement)).toBe(true) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/components/Header.spec.ts` around lines 44 - 55, The test only checks rendering but not autofocus behavior; after mounting Header (with open: true, mode: 'modal', menu: { portal: false }) await rendering (nextTick or mountSuspended resolution), then locate the element that should receive autofocus (e.g., find('input[autofocus]') or querySelector('[autofocus]') / a known data-testid inside the Header component) and assert that document.activeElement is that element or that the modal contains document.activeElement to verify focus moved into the modal; update the spec in Header.spec.ts to perform this focused-element assertion after mount to catch regressions in focus trapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/components/Header.spec.ts`:
- Around line 44-55: The test only checks rendering but not autofocus behavior;
after mounting Header (with open: true, mode: 'modal', menu: { portal: false })
await rendering (nextTick or mountSuspended resolution), then locate the element
that should receive autofocus (e.g., find('input[autofocus]') or
querySelector('[autofocus]') / a known data-testid inside the Header component)
and assert that document.activeElement is that element or that the modal
contains document.activeElement to verify focus moved into the modal; update the
spec in Header.spec.ts to perform this focused-element assertion after mount to
catch regressions in focus trapping.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f82ccd49-febe-4a6c-aa0d-0013d7bd8829
📒 Files selected for processing (2)
src/runtime/components/Header.vuetest/components/Header.spec.ts
commit: |
Verify the dialog element with role="dialog" is rendered when the modal menu is open, addressing CodeRabbit review feedback.
|
When does that happen exactly? You should not be able to navigate to the mobile menu with keyboard on mobile 🤔 |
Even on mobile layouts, the menu can still be used with a keyboard (e.g. via bluetooth keyboards) or assistive technologies like screen readers. Without a proper focus trap, keyboard and screen reader users (especially visually impaired or blind people) can navigate outside the open menu, which breaks the expected interaction and makes it confusing to use. Also, since this applies to all viewports below the |
|
As @Oui-Dev mentioned, this isn't just about touch-only mobile. The mobile menu shows up for all viewports below The issue is Removing the handler lets reka-ui do its thing - first focusable element gets focused on open, standard dialog behavior. Anyone who needs the old behavior can still opt out: <UHeader :menu="{ content: { onOpenAutoFocus: (e) => e.preventDefault() } }" /> |
|
I see, would you mind applying the same changes to DashboardSidebar and Sidebar which have the same behavior then? (I don't think the test is necessary though) |
… proper focus trapping Apply the same fix from Header to DashboardSidebar and Sidebar components. Remove unnecessary Header modal test per maintainer feedback.
|
Done - applied the same change to |
|
Is it also possible to modify the doc at the same time regarding this point? Or would you prefer that I open a separate issue? |
|
That doc mismatch looks like a separate concern from the focus trap fix here - probably best as its own issue so it gets tracked properly. Feel free to tag me (@faizkhairi) if you open one, happy to take a look. |

Description
Fixes #6257
When
UHeader,UDashboardSidebar, andUSidebaropen their mobile menu modal via keyboard, focus should automatically move inside the modal content. Instead, theonOpenAutoFocushandler inmenuPropscallede.preventDefault(), which suppressed reka-ui's Dialog auto-focus behavior. This caused Tab navigation to traverse background page content before the focus trap engaged.Root Cause
All three components configured the menu's content props with:
This blocks the default WAI-ARIA Dialog focus management that reka-ui provides.
Fix
Removed the
onOpenAutoFocushandler frommenuPropsdefaults inHeader.vue,DashboardSidebar.vue, andSidebar.vue, allowing reka-ui to handle focus management correctly on modal open (auto-focus first focusable element inside the dialog).Opt-out
Users who need the previous behavior can still suppress auto-focus via the
menuprop:Since
defumerges user-provided values with highest priority, this overrides the default.WCAG Reference