inert, not aria-hidden, and not clipped from view by an ancestor that cannot scroll to reveal it.Tab traversal remains available by default so keyboard and assistive technology users are not trapped. Use a separate FocusNavigationProvider with blockTabNavigation only for an intentionally arrow-only experience.Escape returns focus to the first eligible descendant. This supports desktop keyboard testing; it is not the device Back path. The provider does not consume the event, so application-level Back handling can still run.import { useFocusNavigation } from '@wearables-ui-toolkit/mrbd';
const { moveFocus, activateFocused, clearFocus, isActive } = useFocusNavigation();
| Member | Behavior |
|---|---|
moveFocus(direction) | Runs one directional search for 'up' \| 'down' \| 'left' \| 'right'; returns whether anything moved. |
activateFocused() | Activates the element that currently has focus. |
clearFocus() | Focuses the first eligible element when focus has left the provider’s container or rests on the container itself. Does nothing while a descendant control owns focus. |
isActive | Whether focus navigation is enabled. |
App enables automatic initial focus. After a route settles, the focus coordinator selects the top-left eligible interactable in the active root. It leaves an existing page focus owner unchanged and skips elements on a page that is animating out.initialFocusEligible={false} on an element that must not become the entry point.PageTransition has no saved focus to restore, it selects initial focus for the page entering the viewport. Its initialFocus prop defaults to 'top-left' and also accepts 'none', { selector }, or a function that receives the page element and returns the element to focus.| Prop | Default | Purpose |
|---|---|---|
partialFocusSupportedAxis | PartialFocusSupportedAxis.XY | Which axes give rubber-band and handoff feedback. Also X, Y, and None. |
isRubberbandTranslationEnabled | true | When false, rubber-band feedback updates the material origin without translating the element. |
onInvalidFocusDirection | — | Called with the direction when directional input could not move focus. |
onPartialFocusHandoff | — | Called when focus moves between interactables so the material can render the handoff. |
ScrollView verifies this before aligning the boundary. If non-focusable content before or after the control would move it outside the safe area, focus reveal takes priority; continued outward arrow input scrolls the remaining content incrementally.ScrollView also responds to PageUp, PageDown, Home, and End.Escape exercises the same component and application-level Back path. The innermost layer that owns Back handles it so a lower layer does not act on the same request.| Order | Layer | What happens |
|---|---|---|
1 | A focusable tooltip popup, including a VerticalMenu anchored with getVerticalMenuAnchorProps | Owns a temporary same-URL history entry. Back dismisses the popup and returns focus to its anchor before the previous application route can be reached. |
2 | An open menu that has focus | ContextMenu calls onDismiss(DismissReason.BACK_BUTTON); VerticalMenu calls onDismissRequest(VerticalMenuDismissReason.ESCAPE). Desktop Escape follows this same dismissal path. |
3 | Pager or SubNavigationPager | With useBackButtonForHome (default true), a homeIndex of zero or greater, and a current page that is not already home, returns to the home page instead of leaving the route. On the home page it claims nothing, so Back falls through. |
4 | The application | Your useBackNavigation handler, or the router adapter, handles route return. |
Modal visibility; Modal does not claim Back automatically. Close it from the application’s Back handler and return focus to the control that opened it, preserving the user’s navigation context.useBackNavigation once at the application boundary for any Back request that reaches the route. Return false when navigation cannot continue so the request remains unclaimed. Any other return value claims it. On desktop, an unclaimed Escape key runs the same handler.import { useBackNavigation } from '@wearables-ui-toolkit/mrbd';
useBackNavigation(() => {
if (!canGoBack) {
return false;
}
goBack();
});
ReactRouterNavigationProvider connects application-level Back to router navigation. System Back traverses browser history; desktop Escape and imperative navigateBack() calls use the same route-return and restoration path. At the first history entry, the provider navigates to fallbackTo ('/' by default). A control that needs the same behavior can read it directly:import { useReactRouterNavigation } from '@wearables-ui-toolkit/mrbd/react-router';
const { navigateBack } = useReactRouterNavigation();
replace for genuine redirects. Before a returning page is revealed, restore the route, focused element, visual focus state, scroll offset, and any pager or carousel index. Focus paired with a stale scroll offset can leave the restored target outside the visible rectangle, so these values must be restored as one state.PageTransition handles the DOM focus-and-scroll portion of this lifecycle. It retains outgoing and incoming pages during the animation. With preservePageState (true by default), it captures scroll offsets and one positional focus index in the latest snapshot for each transition key, then restores both before showing a returning page. It does not preserve router data, React component state, form models, pager or carousel indices, or separate snapshots for multiple history entries that share a key. When no snapshot exists, initialFocus chooses the focus target.preserveFocusedInteractableDuringNavigation() opens a short retention window that holds the state and returns a function that closes the window early. Open the window when navigation begins and close it after the destination appears; PageTransition’s onTransitionEnd is the typical release point. Do not release it in the same tick as navigation, because the window would close before the browser moves focus. The window expires automatically, so a missed release does not leak.import { useRef } from 'react';
import { preserveFocusedInteractableDuringNavigation } from '@wearables-ui-toolkit/mrbd';
const releaseRef = useRef<(() => void) | null>(null);
function startNavigation(nextPath: string) {
releaseRef.current?.();
releaseRef.current = preserveFocusedInteractableDuringNavigation();
navigate(nextPath);
}
function handleTransitionEnd() {
releaseRef.current?.();
releaseRef.current = null;
}
Pager uses arrow input along its orientation to move between pages and reports each entry with a NavigationDirection. A page declares lifecycle behavior by wrapping content in PagerPage or calling usePagerPageLifecycle from within the page.| Lifecycle member | Behavior |
|---|---|
onRequestInitialFocus(request) | Return true to claim initial focus for the incoming page; otherwise the pager focuses that page’s first focusable. |
shouldPreventNavigation() | Return true to veto a keyboard- or focus-driven page change. |
onWillShowPage / onWillHidePage | Fire around the page becoming or ceasing to be current. |
onWillUnloadPage | Fires before an inactive page is unmounted under unmountInactivePages. |
onAnimationCompleted(request) | Fires after an animated transition into the page finishes. |
onWillInterceptBack() | Return true to consume Back before the pager’s own back-to-home handling. |
requestInitialFocusOnMount is false, so the first page does not claim focus unless requested. A change through the controlled currentPageIndex prop is forced navigation and bypasses shouldPreventNavigation, because the application owns that state. If a page needs to veto such a change, gate the state update in the application. Set navigationLocked to reject requested page changes, and use onNavigationAttemptWhileLocked to communicate why. Page-set reconciliation can still move the resolved index when the previous page no longer exists.SubNavigationPager adds a tab bar and coordinates focus with the current page:ArrowDown from the tab bar focuses the first focusable element in the current page.autoHide defaults to false. When enabled, it fades the bar after inactivity and restores it on focus.