Foundations
Focus Trap
BetaSince 0.1.0
Keeps keyboard focus inside its container, for modal content such as Dialog.
import { FocusTrap } from "@anantya/uix";Overview
FocusTrap keeps keyboard focus inside its container while it is active, so Tab and Shift+Tab can't reach the page behind it. UIX components use it for modal content, such as Dialog.
Building block
For a modal window, use Dialog, or Drawer, coming in v1, for a side panel. Both trap focus already.
Missing a component? Ask for it instead of building your own.
Signature
function FocusTrap(props: FocusTrapProps): ReactElement;
interface FocusTrapProps extends HTMLAttributes<HTMLElement> {
active?: boolean;
asChild?: boolean;
initialFocus?: RefObject<HTMLElement | null>;
loop?: boolean;
restoreFocus?: boolean;
ref?: Ref<HTMLElement>;
}Parameters
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | true | Keeps focus inside while true. |
asChild | boolean | false | Uses its one child element as the container, such as an <aside>, instead of a <div>. |
initialFocus | RefObject<HTMLElement | null> | - | The element to focus when it activates. |
loop | boolean | true | Tab on the last element moves to the first, and Shift+Tab on the first to the last. When false, focus stops at both ends. |
restoreFocus | boolean | true | Returns focus, when it deactivates, to the element that had it before. |
ref | Ref<HTMLElement> | - | Gets the container. |
| Any HTML attribute | HTMLAttributes<HTMLElement> | - | Passed to the container, such as role and aria-labelledby. |
Returns
A <div tabindex="-1">, or its child with asChild, around its children. While it is active:
- It focuses
initialFocusfirst, else the first element withdata-autofocus, else the element already focused inside, else the first one Tab reaches, else the container - Tab and Shift+Tab move through the elements inside, found again on every press, so ones added later count
- Focus that lands outside, such as from a click, is pulled back inside
- When the focused element is removed, the container takes focus
When it deactivates or unmounts, focus returns to the element that had it before. When one FocusTrap is inside another, the inner one is in control.
Example
Turn on Keep focus here, then press Tab: focus moves through the switch and the three buttons and wraps at the ends. Turn it off to let focus leave.
Code
"use client";
import { Button, FocusTrap, Switch } from "@anantya/uix";
import { useState } from "react";
export default function InvoiceActions() {
const [active, setActive] = useState(false);
return (
<FocusTrap
active={active}
className="flex flex-col items-center gap-4 rounded-xl border border-border p-4 text-sm"
>
<label className="flex items-center gap-3">
<Switch checked={active} onCheckedChange={setActive} />
Keep focus here
</label>
<div className="flex flex-wrap justify-center gap-2">
<Button variant="secondary">Download PDF</Button>
<Button variant="secondary">Send reminder</Button>
<Button>Mark as paid</Button>
</div>
</FocusTrap>
);
}Notes
- FocusTrap only moves keyboard focus. A modal also needs a role, a name, the rest of the page made inert and a way to close it with Esc, which Dialog has
- Content rendered inside it through a Portal counts as inside, so a menu opened from inside can take focus
- It does nothing on the server; it activates once the page is hydrated
Changelog
Every change to Focus Trap since 0.1.0 is in the release notes.