FocusScope
The focus-confinement primitive. Moves focus in on mount, keeps Tab inside while trapped, and returns focus to where it was on unmount — the piece every modal overlay is built on.
Basic usage
Install the package:
pnpm add @design-schema/react @design-schema/tokens Import the stylesheet once for the app, then the component:
import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { FocusScope } from '@design-schema/react'; Examples
Confirm your changes
import { FocusScope, Button, Text } from '@design-schema/react';
<FocusScope>
<>
<Text>Confirm your changes</Text>
<Button label="Cancel" />
<Button label="Continue" />
</>
</FocusScope>Props
6 rows| Prop | Type | Default | Description |
|---|---|---|---|
| children required | content | — | The confined content. The scope renders no element of its own beyond a wrapper that is not focusable and not announced. |
| trapped | boolean | true | Tab and Shift+Tab wrap within the scope's focusable descendants, and focus that lands outside is pulled back in. False turns the scope into a plain "move focus in and restore on exit" helper, for non-modal panels. |
| autoFocus | first | last | container | none | first | Where focus goes on mount: the first focusable descendant, the last, the scope's own wrapper (made focusable with tabindex -1, for reading-first dialogs), or nowhere. |
| restoreFocus | boolean | true | On unmount, focus returns to the element that was focused when the scope mounted, or to the next focusable element in the document if that one is gone. |
| returnFocusTo | object RefObject<HTMLElement | View> | — | Explicit element to restore focus to instead of the recorded opener. Required on native when the opener is not a TextInput (React Native exposes no generic "currently focused element"), so every overlay passes its trigger ref. `shape` is a cross-platform sketch, never the literal type a platform emits; each types it with its own instance: `RefObject<HTMLElement | null>` on web, `HTMLElement | Ref<HTMLElement>` (lit/directives/ref.js) as a non-attribute property on Lit, `RefObject<ViewInstance | null>` on React Native. |
| active | boolean | true | Pause the scope without unmounting it — used while a nested scope (a Menu inside a Dialog) is open, so the innermost active scope owns Tab. |
Events
1 row| Event | Type | Description |
|---|---|---|
| onEscapeAttempt | onEscapeAttempt / escape-attempt | Fired when trapped focus would have left the scope (Tab from the last element, Shift+Tab from the first) just before it wraps, with the direction. Diagnostic; components do not need it. |
Accessibility
No implicit ARIA role — the component is styling and structure, and adds nothing to the accessibility tree.
Anatomy
What generation guarantees
Focus trap — While it is open, Tab cycles inside it; focus cannot reach the page behind.
Focus restore — On close, focus returns to whatever opened it.
Keyboard operable — Everything the pointer can do here, the keyboard can do — see the keyboard section.
This component declares no contrast pair of its own — it inherits the colour of whatever it is placed in — and it does not animate.