The HTML shadowrootdelegatesfocus attribute
The HTML shadowrootdelegatesfocus attribute makes a declarative shadow root delegate focus. When the host is focused with focus(), or a non-focusable part of its shadow tree is clicked, focus moves to an element inside, the one with autofocus if there is one and otherwise the first focusable element.
Overview
The shadowrootdelegatesfocus attribute tells a shadow host to pass focus on to an element in its shadow tree. It is a boolean attribute on a <template> with shadowrootmode, and it sets the shadow root's delegatesFocus flag, the same option attachShadow() accepts.
The HTML Standard picks the target with its focus delegate rules. It looks first for an element with autofocus in the shadow root, and otherwise takes the first focusable element in tree order. Calling host.focus() and clicking a non-focusable part of the component both end up there.
A delegating host is never a focusable area in its own right, so focus can only land on something inside it. MDN notes that the host still gets any :focus styling while that happens, which lets page CSS outline the whole component around the focused control.
Keyboard navigation follows its own path. The standard builds the sequential focus order around delegating hosts and never runs the focusing steps on them during Tab navigation. MDN's compatibility data lists the attribute in Chrome and Edge 123, Firefox 123 and Safari 16.4.
Syntax
<email-field>
<template shadowrootmode="open" shadowrootdelegatesfocus>
<label>Email <input type="email"></label>
</template>
</email-field>
Values
| Value |
|---|
| A boolean attribute: present or absent. |
Example
<div id="plain"><template shadowrootmode="open"><p>Plain host</p><input aria-label="Plain input"></template></div>
<div id="delegating"><template shadowrootmode="open" shadowrootdelegatesfocus><p>Delegating host</p><input aria-label="Delegated input"></template></div>
<button type="button" id="go">Call focus() on both hosts</button>
<p id="out"></p>
<script>
document.getElementById('go').addEventListener('click', () => {
const plain = document.getElementById('plain');
const delegating = document.getElementById('delegating');
plain.focus();
const a = plain.shadowRoot.activeElement;
delegating.focus();
const b = delegating.shadowRoot.activeElement;
document.getElementById('out').textContent =
'Plain host: ' + (a ? 'input focused' : 'nothing focused') + '. Delegating host: ' + (b ? 'input focused' : 'nothing focused') + '.';
});
</script>
Best practices
- Use
shadowrootdelegatesfocuson components that wrap one main control, such as a custom text field, so callingfocus()on the element moves focus to that control. - Put autofocus on the inner element that should receive focus when the first focusable element is the wrong one, such as a close button that comes before the main field.
- Style the component's focus state on the host with
:focus, which the host matches while focus is delegated inside it. - Test with the keyboard as well as the mouse, because delegation changes what happens on click and on
focus(), while Tab order is built separately. - For shadow roots created in script, pass
delegatesFocus: truetoattachShadow()instead.
Accessibility
MDN notes that focus is of particular importance for keyboard users, including those using screen readers. With delegation, calling focus() on the host moves focus to the real control inside it.
Choose the target deliberately. MDN warns that using autofocus to send focus to an element later in the DOM can make people skip important content that comes before it.























