Skip to content

fix(web-components): avoid dialog from focusing on non-active tab upon showing#36278

Open
marchbox wants to merge 10 commits into
microsoft:masterfrom
marchbox:users/machi/tablist-activeid
Open

fix(web-components): avoid dialog from focusing on non-active tab upon showing#36278
marchbox wants to merge 10 commits into
microsoft:masterfrom
marchbox:users/machi/tablist-activeid

Conversation

@marchbox
Copy link
Copy Markdown
Contributor

@marchbox marchbox commented Jun 2, 2026

Previous Behavior

When having this structure:

<fluent-dialog>
  <fluent-dialog-body>
    <fluent-tablist activeid="tab2">
      <fluent-tab id="tab1">tab 1</fluent-tab>
      <fluent-tab id="tab2">tab 2</fluent-tab>
      <fluent-tab id="tab3">tab 3</fluent-tab>
    </fluent-tablist>
  </fluent-dialog-body>
</fluent-dialog>

When the dialog is open, in Chromium and WebKit, tab1 becomes selected and activeid’s value becomes tab1 automatically.

The root cause is:

  • <fluent-dialog> uses an HTML <dialog> in its shadow root, when FluentDialog::show() is called, it calls <dialog>.show() or <dialog>.showModal()
  • When a <dialog> is open, browsers will determine which element gets initially focused
  • In Chromium and WebKit, the first focusable element receives the initial focus, regardless of its tabindex value
  • The first tab, which has tabindex=-1, receives the initial focus
  • In <fluent-tablist>, its focusin event handler changes the activeid attribute to the ID of the tab that just receives the focus

This won’t be an issue if the <fluent-dialog-body>, or any slotted content/descendant of the <dialog> in shadow root, contains another focusable element prior to the tablist, e.g. the dialog close button.

New Behavior

Added a <div tabindex=-1></div> element before any slotted content of <fluent-dialog>, this makes sure the <fluent-tab> with tabindex=-1 won’t receive the initial focus and cause the issue.

Accessibility

This effectively puts the focus on the <dialog>, in my VoiceOver test, it’d read out the dialog role and its label, if any. Hitting Tab key again will move focus to the next focusable element, e.g. a tablist. And if any element in the dialog content has autofocus attribute, the empty div will not prevent that element from being auto focused.

Other solutions considered:

  1. On <dialog>’s beforetoggle event, if it doesn’t contain any element with autofocus attribute, add autofocus to the <dialog> itself. 2 issues with this:
    • This isn’t quite correct, we should only add autofocus to the <dialog> itself if it doesn’t contain any focusable element, which isn’t our case
    • While this does work in Firefox and Safari, Chromium ignores the autofocus attribute as long as there’s any focusable element in its content
  2. Check if there’s any <fluent-tablist> used in <fluent-dialog-body>, and add autofocus attritube to the <fluent-tab> element based on activeid. 2 issues:
    • We can’t make the assumption that the tablist should be auto focused, and performing a thorough check could slow down performance
    • This approach makes the solution very narrow to solving for <fluent-tablist>, it does nothing to the root cause
  3. In <fluent-tablist>’s focusin event handler, check if the event.relatedTarget is contained within the tablist itself, if not, don’t update activeid. This doesn’t work well with the focusgroup polyfill, since the polyfill would still set the “current” element to the focused tab in its focusin event handler, so this will create an inconsistency between the component and the polyfill. Fixing this may require a somewhat significant update to the polyfill.

Related Issue(s)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

📊 Bundle size report

✅ No changes found

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Pull request demo site: URL

@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown

@github-actions github-actions Bot Jun 2, 2026

Choose a reason for hiding this comment

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

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-web-components/Badge 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-web-components/Badge. - Dark Mode.normal.chromium.png 443 Changed
vr-tests-web-components/MenuList 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-web-components/MenuList. - RTL.1st selected.chromium_2.png 38479 Changed

Copy link
Copy Markdown
Contributor

@davatron5000 davatron5000 left a comment

Choose a reason for hiding this comment

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

Mixed feelings about the empty <div> but makes sense given the situation.

Are we pretty sure this is localized to how the native <dialog> handles focus managenet? Another way to put it, is this an issue with fluent-tablist in other contexts like a dropdown?

@marchbox
Copy link
Copy Markdown
Contributor Author

marchbox commented Jun 4, 2026

Mixed feelings about the empty <div> but makes sense given the situation.

Are we pretty sure this is localized to how the native <dialog> handles focus managenet? Another way to put it, is this an issue with fluent-tablist in other contexts like a dropdown?

@davatron5000 I believe so. The issue is caused by browsers proactively manages the focus for <dialog>, they don’t do that for popovers.

Also this is more of an issue with the focusgroup polyfill than <fluent-tablist>. The native focus management also breaks, or at least makes it unexpected for, native implementation of focusgroup, if you open this pen in Canary: https://codepen.io/marchbox/pen/JobMrZE, and click the "open dialog with focusgroup" button, the second button has focusgroupstart but it’s the first button that gets the initial focus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Bug]: fluent-tablist inside a fluent-dialog-body seems to ignore this activeid attribute

2 participants