FocusNavigationProvider

Updated: Sep 24, 2026
Prefer the focus root that App provides for a typical application. App scopes the provider to its content root and enables automatic initial focus, which gives Meta Ray-Ban Display users consistent directional navigation without additional setup.
FocusNavigationProvider owns directional focus for a subtree. It responds to arrow keys by moving focus to the nearest eligible element. If no candidate is available, it offers movement to the owning scroll container and then reports a boundary to the focused control for rubber-band feedback.
Render another provider only when a different container needs its own focus scope, such as a toolkit surface embedded in a host-owned layout, or when a surface uses arrow-key-only traversal. Use useFocusNavigation() when the application needs to request focus movement through the active provider.

Usage

Pass an existing element to scope focus queries without adding a wrapper:
import { useRef, type ReactNode } from 'react';
import { FocusNavigationProvider } from '@wearables-ui-toolkit/mrbd';

export function EmbeddedSurface({ children }: { children: ReactNode }) {
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <FocusNavigationProvider containerRef={containerRef}>
      <div ref={containerRef} className="embedded-surface">
        {children}
      </div>
    </FocusNavigationProvider>
  );
}
Use the provider API for programmatic movement instead of synthesizing key events:
import { Button, useFocusNavigation } from '@wearables-ui-toolkit/mrbd';

export function AdvanceControl() {
  const { moveFocus } = useFocusNavigation();

  return <Button title="Next" onActivate={() => moveFocus('down')} />;
}
Import FocusNavigationProvider from the package root; it has no per-component subpath.

Props

The full prop type is FocusNavigationProviderProps. The component accepts no DOM attributes and does not forward a ref.
PropTypeDefaultDescription
children
ReactNode
—
Required. The subtree that participates in focus navigation.
enabled
boolean
true
Turns the provider’s key and pointer handling on or off. When off, useFocusNavigation().isActive reports false.
containerRef
RefObject<HTMLElement \| null>
—
Existing element to scope focus queries to. Without it, the provider renders its own full-size wrapper.
blockTabNavigation
boolean
false
Blocks native Tab traversal so movement is arrow keys only.
autoFocusFirst
boolean
false
Lets the focus coordinator claim the top-left eligible interactable on mount and on page change. App enables this.

Hook

useFocusNavigation() returns the nearest provider’s contract. Outside a provider, it returns an inert value: isActive is false, moveFocus returns false, and the other two members have no effect.
MemberTypeDescription
isActive
boolean
Whether the provider is enabled.
moveFocus
(direction: 'up' \| 'down' \| 'left' \| 'right') => boolean
Runs one directional search. Returns whether anything moved — either focus changed or a scroll container consumed the movement.
activateFocused
() => void
Activates the element that currently has focus.
clearFocus
() => void
Pulls focus back to the first eligible control when focus has escaped the container or rests on the container element itself. Does nothing while a control inside the container already owns focus.

Behavior

Resolution order

The provider resolves each arrow key in three steps. It first finds the nearest eligible element in that direction, beginning in the innermost focus section that contains the current element and widening outward. If no candidate exists, it asks the owning scroll container to move. If scrolling cannot satisfy the request, it signals the focused control to produce boundary feedback.

Candidate selection

Directional navigation prefers candidates whose box overlaps the beam in the requested direction, then chooses the shortest weighted distance. This keeps movement spatially predictable instead of following DOM order. Hidden, inert, aria-hidden, fully transparent, zero-sized, or clipped outside their scroll viewport are not candidates. Elements that resolve to pointer-events: none are also excluded. The provider reads only the element’s resolved pointer-events, so a control with pointer-events: auto remains reachable even when an ancestor disables pointer events.

Repeated input

Each directional key event runs one movement step synchronously. Repeated arrow keys therefore traverse consecutive candidates without waiting for focus animations to finish.

Focus outside the container

When an arrow key arrives while focus is outside the provider’s container, the provider focuses the first eligible element instead of advancing directionally. If the container has no eligible element, it offers the key to the nearest scroll container so content-only pages can still scroll.
Escape uses the same restoration behavior. If focus rests on the container element rather than a control inside it, the provider moves focus to the first eligible element. If a control inside already owns focus, nothing changes. The provider does not consume Escape, so host Back handling continues to run.

Activation keys

The browser owns Enter and Space, allowing the focused control’s native activation behavior to run. Only activateFocused() requests a click on the caller’s behalf.
While focus is inside a focusable tooltip, that layer owns key handling and the provider remains inactive. This prevents the surrounding page from competing with overlay focus behavior.

Pointer behavior

Where mouse input is expected, a primary-button click on empty space moves focus to the focusable element nearest the click point. This aligns pointer and directional focus behavior. The browser owns clicks that land on or inside a focusable element, and the provider ignores clicks inside a focusable tooltip. On Meta Ray-Ban Display, the provider disables this behavior so a synthetic tap does not move directional focus.

Container

With containerRef, the caller’s element owns the focus region and the provider renders no wrapper. Without containerRef, the provider renders a full-size div that is programmatically focusable but is not a tab stop.

Initial focus

With autoFocusFirst, the focus coordinator focuses the top-left eligible control on the active page. It runs only when the page does not already contain focus, so user, PageTransition, and host-owned focus take priority. Set initialFocusEligible={false} to exclude a control from entry focus while keeping it reachable through directional navigation.

Nesting

When providers are nested, the provider for the innermost container that holds the focused element owns navigation. This lets an embedded surface manage its focus scope without changing the application root.

Accessibility

  • Keep blockTabNavigation at its default false for web surfaces. Keyboard and assistive-technology users must be able to leave the region. Enable it only for a surface that is exclusively driven by directional input.
  • The provider adds no role, landmark, or accessible name. Components inside the region own those semantics.
  • The provider calls preventDefault() for arrow keys it handles, preventing the browser from scrolling for the same input.
  • moveFocus gives the destination the same visual state as arrow-key movement, which keeps the visible focus indicator aligned with actual focus.
  • Disabled controls remain focusable so directional navigation preserves the screen position of unavailable actions.

Application responsibilities

  • Prefer App as the focus-root owner in a typical application.
  • Pass containerRef when a host element already defines the focus region.
  • Use moveFocus, activateFocused, and clearFocus instead of dispatching synthetic key events, so provider state and visible focus remain consistent.
  • Arrange each control with a sensible neighbor in every direction users will navigate.

Avoid

  • Avoid adding a second provider around the entire application because App already owns that focus scope.
  • Keep blockTabNavigation off on surfaces that keyboard users need to leave; enabling it there would trap focus within the surface.
  • Avoid adding a parallel arrow-key handler because it can compete with the provider’s focus and scrolling decisions.
  • Avoid changing a control’s tab index to hide it from directional navigation. Use initialFocusEligible={false} to exclude it from entry focus, or disable it when the action is unavailable.