Vize

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.