Focus Manager
The Focus Manager handles the focus for its descendants. It allows focusing inside the scope of a group of HTML elements and restoring the focus back to the previously focused element. Besides this, there's also the possibility to auto-focus on the first "tabbable" element inside the scope.
- Press
tab
to focus on the "Show Menu" button. - Press
enter
to open the menu. - Focus will be set on the "Close" button
- Press
tab
orshift+tab
to go through each focusable element on the menu. - Press
enter
on the "Close" button to exit the menu. - Focus will be set on the "Show Menu" button again.
Development Instructions
When you need to re-open the containment, the restoreFocus
can help you out when you need to reset the focus back to the previous element, like the button where you clicked to open a menu, for instance.
The component works primarily with the tab
key so that you can go back and forth between elements that can receive focus. However, for cases where you need to move focus within the scope programmatically (e.g. with the arrow or the home
or the end
keys), the useFocusManager
hook can be used to handle keyboard events and using a focus manager to move focus to the next and previous elements.
1. Wrap the content with the Focus Manager
<FocusManager>
<button type="button">Close</button>
<form>
<label htmlFor="first-name">First Name</label>
<input id="first-name" type="text" name="textfield" />
<label htmlFor="last-name" className="required-field">
Last Name
</label>
<input
id="last-name"
type="text"
name="textfield"
aria-describedby="required-message"
required
/>
<input type="submit" defaultValue="Submit" />
</form>
</FocusManager>
2. Handle its mounting/unmounting on the DOM
function SideMenu() {
const [isOpen, setOpen] = useState(false);
/**
* @param {"open" | "close"} status
*/
function handleOpenStatus(status) {
switch (status) {
case "open":
setOpen(true);
break;
case "close":
default:
setOpen(false);
break;
}
}
function handleOnClick() {
handleOpenStatus("open");
}
function handleOnSubmit() {
handleOpenStatus("close");
}
function handleOnClickToClose() {
handleOpenStatus("close");
}
function renderMenu() {
if (isOpen) {
return (
<FocusManager>
<button onClick={() => setOpen(false)}>Close</button>
<form onSubmit={handleOnSubmit}>
<label htmlFor="first-name">First Name</label>
<input id="first-name" type="text" name="textfield" />
<label htmlFor="last-name" className="required-field">
Last Name
</label>
<input
id="last-name"
type="text"
name="textfield"
aria-describedby="required-message"
required
/>
<button type="button">Submit</button>
</form>
</FocusManager>
);
}
}
return (
<>
<button onClick={handleOnClick}>Open the menu</button>
{renderMenu()}
</>
);
}
Let's recap:
- Clicking the "Open the menu" button below causes the state of
isOpen
to change fromfalse
totrue
. - This, in turn, mounts the
FocusManager
. - Since the
autoFocus
prop is set totrue
, the focus will be automatically placed on the first element that can receive focus, which is the "Close" button. - Clicking the "Close" button unmounts the focus scope, which restores focus back to the button.
3. Add special features mapped to keys (Optional)
If you want to add special navigation features, use the useFocusManager
to programmatically move focus within a FocusManager
.
function SideMenuButton({ children, onClick }) {
const { focusLast, focusFirst } = useFocusManager();
const onKeyDown = (event) => {
switch (event.key) {
case 'Home':
focusFirst();
break;
case 'End':
focusLast();
break;
default:
break;
}
return <button type="button" onClick={onClick} onKeyDown={onKeyDown}>{children}</button>;
};
}
function SideMenu() {
const [isOpen, setOpen] = useState(false);
{...}
function handleOnSubmit() {
handleOpenStatus("close");
}
function handleOnClickToClose() {
handleOpenStatus("close");
}
function renderMenu() {
if (isOpen) {
return (
<FocusManager>
<SideMenuButton onClick={handleOnClickToClose}>Close</SideMenuButton>
<form onSubmit={handleOnSubmit}>
{...}
<SideMenuButton>Submit</SideMenuButton>
</form>
</FocusManager>
);
}
}
return (
<>
<button onClick={handleOnClick}>Open the menu</button>
{renderMenu()}
</>
);
}