open-ui icon indicating copy to clipboard operation
open-ui copied to clipboard

[focusgroup] Interaction with custom elements that delegate focus

Open jpzwarte opened this issue 2 months ago • 5 comments

How does the focusgroup interact with custom elements that delegate focus to an element inside the shadow root?

<div focusgroup>
  <host-element>
    <template shadowrootmode="open" shadowrootdelegatesfocus>
      <button>Inside button</button>
    </template>
  </host-element>
  <button>Outside button</button>
</div>

Will this "just" work? Or will focus be trapped on the inside button?

jpzwarte avatar Oct 08 '25 06:10 jpzwarte

I think the idea for it is to "just work". The second point in the focusgroup explainers use case section states that focusgroup can be declared on a shadow host or an ancestor and it should still work. I explored a bit how this could work in an experimental polyfill implementation (based on the old explainer but will update to the new one soon): https://gfellerph.github.io/focusgroup-polyfill/#shadow-dom-focusgroup in case you want to try it.

gfellerph avatar Oct 08 '25 20:10 gfellerph

+1 this will "just work". focusgroup will consider all focusable elements within the focusgroup's scope as a focusgroup item, including shadow trees. If you as the developer didn't want this, you can use focusgroup="none" to opt out.

janewman avatar Oct 08 '25 20:10 janewman

Just thought of another scenario i have in our design system: an element that is part of the focusgroup, but if you're on that element, pressing TAB will focus an action button that is also in the shadow root, but the action button should not be focusable using the arrow keys.

If i read https://open-ui.org/components/scoped-focusgroup.explainer/#opting-out correctly, i have to add the focusgroup="none" attribute to the parts of the custom element i want excluded right? But TAB'ing would still work?

So the DOM would look like this:

<div focusgroup>
  <host-element>
    <template shadowrootmode="open" shadowrootdelegatesfocus>
      <button>Primary inside button</button>
      <button focusgroup="none">Secondary inside button</button>
    </template>
  </host-element>
  <button>Outside button</button>
</div>

jpzwarte avatar Oct 09 '25 08:10 jpzwarte

Tabbing would work like this (taking your example):

  1. User tabs into focusgroup, "Primary inside button" gets focus because it's the first element in DOM order
  2. Pressing the right arrow key moves focus to the "Outside button" because "Secondary inside button" is opted out of focusgroup behaviour
  3. Pressing shift + tab focuses the "Secondary inside button" because this button is opted out of focusgroup and does not participate in the roving tabindex. Arrow keys are not moving focus at this step because this button is considered to be ouside of the focusgroup
  4. Pressing shift + tab again moves focus back to "Primary inside button" due to the segmentation when opting out. An opted out item will split the focusgroup in two, adding one guaranteed tab stop per segment. Arrow keys are enabled again at this step

gfellerph avatar Oct 09 '25 15:10 gfellerph

+1 to @gfellerph's explanation, beat me to it!

janewman avatar Oct 09 '25 15:10 janewman