Focus Scope
Headless Focus Scope; covers React Aria FocusScope, Radix FocusScope, Ariakit FocusTrap.
| Package | @vizejs/ui/focus-scope |
| Maturity | stable |
| Own the source | vize lib pull focus-scope |
| Requires | — |
| Aliases | focus trap, focus containment, focus restoration |
| Covers | React Aria FocusScope, Radix FocusScope, Ariakit FocusTrap |
API
createFocusScope
Create an SSR-safe focus scope with containment, traversal, and restoration.
function createFocusScope(options: FocusScopeOptions): FocusScopeController
containsComposed
function containsComposed(root: Element, element: Element | null): boolean
deepActiveElement
function deepActiveElement(document: Document): HTMLElement | null
focusableElements
function focusableElements( root: Element, options: Pick<FocusScopeMoveOptions, "accept" | "includeProgrammatic"> = {}, ): HTMLElement[]
focusElement
function focusElement(element: HTMLElement, preventScroll = true): void
isUsableTarget
function isUsableTarget(element: HTMLElement | null): element is HTMLElement
createAutoFocusEvent
function createAutoFocusEvent( type: FocusScopeAutoFocusEvent["type"], target: HTMLElement | null, ): FocusScopeAutoFocusEvent
capture
function capture(errors: unknown[], callback: () => void): void
surfaceErrors
function surfaceErrors(errors: readonly unknown[], message: string): void
readBoolean
function readBoolean(source: unknown, name: string): boolean
readRoot
function readRoot(source: unknown): Element | null
readTarget
function readTarget(value: unknown, name: string): HTMLElement | null
validateOptions
function validateOptions(options: FocusScopeOptions): void
resolveMoveOptions
function resolveMoveOptions(value: FocusScopeMoveOptions | undefined): ResolvedMoveOptions
comparePositiveTabindex
Order positive tabindex values first and ascending, leaving ties to the caller.
function comparePositiveTabindex(left: HTMLElement, right: HTMLElement): number
documentOrder
function documentOrder(elements: readonly HTMLElement[]): HTMLElement[]
createFocusScopeManager
Create stable traversal methods while keeping controller lifecycle orchestration focused.
function createFocusScopeManager({ assertAlive, list, root, }: FocusScopeManagerConfig): FocusScopeManager
attachScope
function attachScope(token: FocusScopeToken, root: Element): void
detachScope
function detachScope(token: FocusScopeToken): void
containmentOwner
function containmentOwner(document: Document): FocusScopeToken | null
rootsOwnedBy
function rootsOwnedBy(token: FocusScopeToken): Element[]
parentScope
function parentScope(token: FocusScopeToken): FocusScopeToken | null
useFocusScope
Create, mount-activate, and scope-dispose a focus scope from Vue setup.
function useFocusScope(options: FocusScopeOptions): FocusScopeController
Behavior
createFocusScope provides the focus lifecycle used by dialogs, popovers, menus, and composite
widgets without owning roles, rendering, or styling. The consumer decides which UI is modal and
combines this primitive with inerting and scroll locking when the interaction requires them.
| Concern | Contract |
|---|---|
| Entry | Optional automatic focus resolves an explicit target, the first programmatically focusable descendant, or an explicit root fallback. |
| Containment | Only the latest containing scope owns document-level recovery and Tab wrapping; non-containing children remain valid portal destinations. |
| Nesting | Scope order is stable across same-document root replacement, and a nested containing scope temporarily supersedes its ancestors. |
| Restoration | The entry target is restored when usable; removed targets fall forward, then backward, then to the parent scope without stealing deliberate outside focus. |
| Traversal | Positive tabindex order, native controls, programmatic targets, radio groups, disabled fieldsets, collapsed details, inert and hidden ancestors are normalized. |
| Shadow DOM | Open shadow roots and assigned slots follow composed-tree order; unassigned light content and independent shadow radio groups stay isolated. |
| Portals | A containing parent owns later non-containing scope roots even when they are not DOM descendants. |
| Reactivity | Root, containment, entry, restoration, and movement filters are read at their documented operation boundaries. |
| Failure atomicity | Entry and restoration failures aggregate after listeners, observers, stack ownership, and reactive state are consistently cleaned up. |
| Server rendering | Setup performs no global DOM access; activation waits for component mount and a nullable root can attach during hydration. |
| Vapor | A public composable fixture must compile without diagnostics in native DOM, SSR, and Vapor lanes. |
| Styling | The module emits no CSS and exposes no style assumptions. |
| Tree shaking | Root and subpath consumers emit identical JavaScript, retain no unrelated families, and emit zero CSS. |