Vize

Focus Interactions

Headless Focus Interactions; covers React Aria useFocus, React Aria useFocusRing, WICG focus-visible.

Package @vizejs/ui/focus
Maturity stable
Own the source vize lib pull focus
Requires interaction-modality
Aliases focus ring, focus within, focus visible
Covers React Aria useFocus, React Aria useFocusRing, WICG focus-visible

API

createFocus

Create an SSR-safe observer for direct or composed-descendant focus.

function createFocus(options: FocusOptions = {}): FocusController

createFocusWithin

Observe composed-descendant focus ownership.

function createFocusWithin(options: FocusWithinOptions = {}): FocusController

createFocusRing

Create direct focus and focus-visible state for a visible ring.

function createFocusRing(options: FocusRingOptions = {}): FocusRingController

useFocus

Create a direct focus observer disposed with the current Vue effect scope.

function useFocus(options: FocusOptions = {}): FocusController

useFocusWithin

Create a focus-within observer disposed with the current Vue effect scope.

function useFocusWithin(options: FocusWithinOptions = {}): FocusController

useFocusRing

Create focus-ring state disposed with the current Vue effect scope.

function useFocusRing(options: FocusRingOptions = {}): FocusRingController

capture

function capture(errors: unknown[], callback: () => void): void

surfaceErrors

function surfaceErrors(errors: readonly unknown[], message: string): void

readBoolean

function readBoolean(value: FocusOptions["isDisabled"], name: string): boolean

validateBoolean

function validateBoolean(resolved: unknown, name: string): boolean

readMode

function readMode(value: FocusMode | undefined): FocusMode

validateOptions

function validateOptions(options: FocusOptions): FocusMode

eventElement

function eventElement(value: EventTarget | null): Element | null

validateElement

function validateElement(value: unknown): Element

composedContains

Test composed ancestry without assuming that both nodes share one realm.

function composedContains(host: Element, candidate: Element | null): boolean

activeElementOf

Resolve the deepest active element through open shadow roots.

function activeElementOf(host: Element): Element | null

ownsFocus

function ownsFocus(mode: FocusMode, host: Element, active: Element | null): boolean

createFocusEvent

function createFocusEvent( type: FocusEvent["type"], mode: FocusMode, host: Element, focusedTarget: Element | null, relatedTarget: Element | null, originalEvent: globalThis.FocusEvent | null, isFocusVisible: boolean, reason: FocusChangeReason, ): FocusEvent

Behavior

Normative state × input → outcome table for @vizejs/ui/focus. Every row is exercised by src/families/accessibility/focus/focus*.test.ts; compile-only assertions live in src/families/accessibility/focus/focus.types.test-d.ts.

# State Input Outcome Proven by
F1 idle target host receives focus immutable focus snapshot and owned state are published direct-focus test
F2 idle target descendant receives focus descendant focus is ignored ownership-mode test
F3 idle within composed descendant receives focus host owns focus and records the deepest active element within/shadow tests
F4 focused within focus moves between composed descendants ownership remains active without duplicate transitions within-boundary test
F5 focused focus leaves the owned boundary immutable blur snapshot includes the destination direct/within tests
F6 focused modality changes from pointer to keyboard focus-visible state updates without a duplicate focus phase modality test
F7 focused after mount autoFocus is enabled visible-ring state remains true regardless of input modality auto-focus test
F8 focused reactive disabled becomes true lifecycle settles without moving DOM focus reactive test
F9 focused reactive disabled resolves to an invalid value lifecycle settles before the stable runtime diagnostic surfaces invalid-reactive test
F10 DOM focused, observer idle explicit refresh ownership and ring state reconcile from the composed active element refresh test
F11 focused manual cancel listeners/state release while DOM focus remains unchanged cancellation test
F12 focused host is removed without blur mutation observer settles leaked ownership removal test
F13 focused host blur delivery is unavailable document focus safety net settles ownership safety-net test
F14 transition callback active callback cancels reentrantly true→false is ordered and no stale focus phase is published reentrancy test
F15 callbacks throw multiple independent callbacks fail state remains settled and failures aggregate after notification callback-failure test
F16 listener setup fails mutation observation cannot start installed document/modality resources roll back setup-failure test
F17 cleanup throws one or more owned resources fail to release every cleanup runs and all failures remain inspectable cleanup tests
F18 effect scope ends any focus hook is scope-owned controller is terminally disposed without user callbacks scope test
F19 concurrent SSR requests identical component trees byte-identical handler-free markup renders without a server document SSR test
F20 SSR followed by hydration host receives and loses focus DOM identity remains and reactive state renders without diagnostics hydration test
F21 DOM, SSR, and Vapor lanes authored consumer compiles all renderer lanes accept the same public props and reactive state renderer-conformance gate
F22 root and subpath consumers only focus is retained equivalent CSS-free bundles exclude unrelated component signatures tree-shaking gate
F23 public TypeScript API state mutation, invalid mode, or callback mismatch compile-only assertions reject misuse src/families/accessibility/focus/focus.types.test-d.ts

Accessibility obligation

The controller normalizes ownership and focus-indicator intent; it never moves focus, invents a role, or emits CSS. Consumers must bind the returned props to the same semantic host, keep keyboard focus order meaningful, and render a visible indicator whenever isFocusVisible is true. autoFocus is reserved for programmatic mount focus where a ring is required even without keyboard modality. Focus styling remains entirely user-controlled.