@wearables-ui-toolkit/mrbd. The final integration section uses the separate @wearables-ui-toolkit/mrbd/react-router subpath.PageTransition as a keyed host around the current route. It renders children in a page layer and observes transitionKey. While the key stays the same, the mounted page receives updated children. When the key changes, the toolkit keeps the outgoing page mounted while it animates out, animates the incoming page in, and then removes the outgoing page.import { PageTransition } from '@wearables-ui-toolkit/mrbd';
<PageTransition transitionKey={route.path} direction={route.direction}>
{renderRoute(route)}
</PageTransition>
aria-hidden and inert, ensuring that only the incoming page receives focus, pointer interaction, or assistive-technology output.PageTransition options| Prop | Type | Default | Description |
|---|---|---|---|
transitionKey | string | required | Identifies the current page. A change starts a transition. |
children | ReactNode | required | Provides the current page content. |
config | PageTransitionConfig | DefaultPageTransitionConfig | Describes enter and exit motion. |
direction | PageTransitionDirection | 'forward' | Chooses config.forward or config.back. |
disabled | boolean | false | Swaps pages without animation. onTransitionEnd still fires. |
initialFocus | PageTransitionInitialFocus | 'top-left' | Selects focus for a page without saved state. Restoration resolves it, so it has no effect when preservePageState is off. |
preservePageState | boolean | true | Captures and restores scroll offsets and focus per key. Turning it off also disables initialFocus. |
timingScale | number \| 'auto' | 'auto' | Multiplies every duration, delay, and settle time. |
onTransitionEnd | () => void | none | Runs after the toolkit removes the outgoing page. |
className | string | '' | Adds a class to the viewport element. |
pageClassName | string | '' | Adds a class to each page layer. |
style | CSSProperties | none | Merges inline styles over the generated transition custom properties. |
PageTransitionProps extends standard div attributes except children and onTransitionEnd, so additional props reach the viewport element. The toolkit installs capture handlers for focus, mouse down, and pointer down to track the most recently interacted control. Caller-provided onFocusCapture, onMouseDownCapture, and onPointerDownCapture handlers still run.configtotalDurationMs, multiplied by the resolved timingScale, the toolkit unmounts the outgoing page and calls onTransitionEnd.children during a transition changes only the incoming page. The outgoing page keeps the content captured when the key changed.transitionKey during an animation restarts toward the newest key. Changing timing props does not restart it; the current transition finishes with its original timing.timingScale'auto' value. It uses standard timing in a browser and a slower scale on Meta Ray-Ban Display glasses because the device compositor needs more time to produce the same perceived motion.timingScale multiplies every delay, duration, and settle time. The toolkit clamps it between one quarter and four times the configured timing. A value that is not finite and positive falls back to 1. Set a number only for a deliberate timing override.@media (prefers-reduced-motion: reduce), so pages change without sliding or fading. The host keeps the outgoing page mounted for the configured settle time, leaving onTransitionEnd timing unchanged.| Type | Shape |
|---|---|
PageTransitionConfig | { forward: PageTransitionMotionConfig; back?: PageTransitionMotionConfig } |
PageTransitionMotionConfig | { enter: PageTransitionStateConfig; exit: PageTransitionStateConfig; totalDurationMs: number } |
PageTransitionStateConfig | { alpha, scale, translateX: PageTransitionValueSegmentConfig } |
PageTransitionValueSegmentConfig | PageTransitionSegmentConfig & { from: number; to: number } |
PageTransitionSegmentConfig | { durationMs: number; delayMs?: number; easing: string } |
PageTransitionDirection | 'forward' \| 'back' |
alpha controls opacity, scale applies a uniform scale to page content, and translateX offsets the page layer horizontally in CSS pixels. Each segment has its own delay, duration, and easing, allowing effects such as starting a slide before a fade. totalDurationMs is the transition’s overall settle time, not the sum of its segments.direction is 'back' and config.back is absent, the toolkit uses the forward motion.| Config | Recommended use |
|---|---|
InAppPageTransitionConfig | Route changes within one application. It defines both directions as a short horizontal slide crossed with a fade and no scaling. Forward moves the incoming page from right to left while the outgoing page leaves to the left; back reverses the motion. |
AppSwitchPageTransitionConfig | Changes between applications or top-level surfaces. It provides a longer, larger forward motion with scaling so the outgoing surface recedes. |
DefaultPageTransitionConfig | An alias of InAppPageTransitionConfig and the default when config is omitted. |
import {
AppSwitchPageTransitionConfig,
PageTransition,
} from '@wearables-ui-toolkit/mrbd';
<PageTransition
transitionKey={surface.id}
config={AppSwitchPageTransitionConfig}
>
{surface.content}
</PageTransition>
import { InAppPageTransitionConfig } from '@wearables-ui-toolkit/mrbd';
import type { PageTransitionConfig } from '@wearables-ui-toolkit/mrbd';
const SlowerForward: PageTransitionConfig = {
...InAppPageTransitionConfig,
forward: {
...InAppPageTransitionConfig.forward,
totalDurationMs: InAppPageTransitionConfig.forward.totalDurationMs * 1.5,
},
};
preservePageState enabled for ordinary routes. When a page is left, the toolkit records scroll offsets and one positional focus index in the latest snapshot for its transitionKey. Returning to that key restores scroll and focus before revealing the page. Two browser-history entries that share a key also share that latest snapshot. This is DOM focus-and-scroll restoration, not general page-state persistence: store router data, React state, form models, selections, and pager or carousel indices separately. The toolkit also synchronizes the control’s focus state so it shows focused material instead of only a browser outline.initialFocus chooses a target when a key has no snapshot, such as on a first visit. The restoration pass resolves this value. With preservePageState off, initialFocus is also disabled and the page owns focus.| Value | Behavior |
|---|---|
'top-left' | Default. Focuses the eligible element nearest the page’s top-left corner. |
'none' | Leaves focus unchanged so the page can manage it. |
{ selector: string } | Focuses the first matching element inside the page. |
(pageElement: HTMLElement) => HTMLElement \| null | Resolves the target in application code. |
PageTransitionInitialFocus is the union of these forms. The resolved element must be focusable and eligible for initial focus. The toolkit skips controls with initialFocusEligible={false}; if a selector or function returns an ineligible element, focus remains unchanged. When focusable content renders lazily, the toolkit retries after the next render, so a Suspense fallback does not prevent initial focus.'forward' when moving deeper, such as opening list details, entering a flow, or pushing a destination onto history.'back' when returning to a destination already behind the current one in history, including a programmatic return to a parent route.RoutePreloadProvider receives a handler.import { RoutePreloadProvider, RoutePreloadTarget } from '@wearables-ui-toolkit/mrbd';
<RoutePreloadProvider preloadRoute={warmRoute}>
<RoutePreloadTarget preloadKey="/settings">
<Button title="Settings" onActivate={openSettings} />
</RoutePreloadTarget>
</RoutePreloadProvider>
| Export | Kind | Purpose |
|---|---|---|
RoutePreloadProvider | component | Owns the preload queue for its subtree. |
RoutePreloadProviderProps | type | { children?: ReactNode; preloadRoute?: RoutePreloadHandler } |
RoutePreloadHandler | type | (preloadKey: string) => Promise<unknown> \| unknown |
RoutePreloadRequest | type | (preloadKey: string) => boolean |
RoutePreloadTarget | component | Renders a div that requests preloading when visible, focused, or hovered. |
RoutePreloadTargetProps | type | Combines target options with standard div attributes. |
useRoutePreloadTarget | hook | Returns the same behavior as bindings for an existing element. |
UseRoutePreloadTargetOptions | type | Defines the hook’s options. |
RoutePreloadTargetBindings | type | Defines the hook’s return value. |
useRoutePreloader | hook | Returns a RoutePreloadRequest for imperative warming. |
AnimationDurations.CONTAINER_STATE_CHANGE, preventing a code download from interrupting rapid movement through a list.preloadRoute, the system remains inactive. useRoutePreloader returns a function that always returns false; the provider adds no key listener; targets create no IntersectionObserver; and targets add focus or pointer handlers only when the caller supplied handlers of its own.RoutePreloadTarget and useRoutePreloadTarget accept the same options:| Option | Type | Default | Description |
|---|---|---|---|
preloadKey | string \| null | none | Supplies the key to preloadRoute. null or omission disables the target. |
preloadOnFocus | boolean | true | Warms when focus enters the target subtree. |
preloadOnPointerEnter | boolean | true | Warms when a pointer enters the target. |
preloadOnVisible | boolean | true | Warms when the target intersects its observation root. |
root | Element \| null | nearest scroll container | Sets the observation root. Pass null to use the viewport. |
rootMargin | string | no margin | Sets the IntersectionObserver root margin. |
threshold | IntersectionObserverInit['threshold'] | 0.01 | Sets the intersection ratio that counts as visible. |
root undefined in a scrolling list. The toolkit then uses the nearest toolkit scroll container, or the viewport when no scroll container exists. A row far below the visible scroll area will not warm merely because it intersects the window. Targets with the same root, margin, and threshold share one observer, and the toolkit stops observing a target after its first successful warm.RoutePreloadTargetBindings<T>: a ref, a data-route-preload-key attribute, and onFocusCapture and onPointerEnter handlers that wrap caller-provided handlers. Spread these bindings onto an existing element:import { useRoutePreloadTarget } from '@wearables-ui-toolkit/mrbd';
import type { ReactNode } from 'react';
function PreloadingSection({
children,
preloadKey,
}: {
children: ReactNode;
preloadKey: string;
}) {
const bindings = useRoutePreloadTarget<HTMLElement>({ preloadKey });
return <section {...bindings}>{children}</section>;
}
false when no provider handler is configured:import { useRoutePreloader } from '@wearables-ui-toolkit/mrbd';
const preload = useRoutePreloader();
preload('/summary');
createPreloadableLazyComponent to pair a lazy component with a preload() function. The preload queue and React rendering then share the same module load:import { createPreloadableLazyComponent } from '@wearables-ui-toolkit/mrbd';
const SettingsRoute = createPreloadableLazyComponent(
() => import('./routes/SettingsRoute'),
module => module.SettingsRoute,
);
PreloadableLazyComponent<Props> has these members:| Member | Type | Description |
|---|---|---|
Component | ComponentType<Props> | Renders the selected component and suspends until the module loads. |
preload | () => Promise<void> | Starts the load or joins one already in progress. |
preload() call or render retries the import. Render Component inside a Suspense boundary because loading suspends.useBackNavigation once at the application boundary for Back requests that reach the route. System Back is the primary path on Meta Ray-Ban Display. During desktop testing, an unclaimed Escape key exercises the same handler:import { useBackNavigation } from '@wearables-ui-toolkit/mrbd';
useBackNavigation(() => {
if (!canGoBack) {
return false;
}
goBack();
});
BackNavigationHandler is () => boolean | void. One application-boundary registration produces predictable routing behavior. System Back follows the host’s browser-history path. On desktop, the hook’s document listener calls the latest handler for an unclaimed Escape key; mounted hooks receive that fallback in installation order, and the first handler to claim it stops later handlers.false claims the request. Returning false leaves it available to the host outside the application.Modal is an exception: it does not claim Back automatically. A route that presents a modal must close it in its application-level handler before navigating.App renders a FocusNavigationProvider around the application, scoped to its content root with automatic initial focus. This provider owns directional input, the primary navigation method on Meta Ray-Ban Display, so most applications do not need another one.FocusNavigationProviderProps is the complete prop type. See FocusNavigationProvider for its behavior and accessibility contract.| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Supplies the subtree participating in focus navigation. |
enabled | boolean | true | Enables or disables provider key and pointer handling. |
containerRef | RefObject<HTMLElement \| null> | none | Scopes focus queries to an element. Without it, the provider renders a full-size wrapper. |
blockTabNavigation | boolean | false | Prevents native Tab traversal for arrow-key-only movement. |
autoFocusFirst | boolean | false | Lets the focus coordinator claim the top-left interactable on mount and page change. |
blockTabNavigation off for ordinary web content. Keyboard and assistive-technology users must not be trapped. Enable it only for a surface whose verified input contract is exclusively directional.useFocusNavigation() reads the nearest provider:| Member | Type | Description |
|---|---|---|
isActive | boolean | Reports whether the provider is enabled. |
moveFocus | (direction: 'up' \| 'down' \| 'left' \| 'right') => boolean | Moves focus one step and reports whether focus or scrolling changed. |
activateFocused | () => void | Activates the currently focused element. |
clearFocus | () => void | Returns focus to the first eligible target after it escapes the container or lands on the container element itself. Does nothing while a descendant already owns focus. |
preserveFocusedInteractableDuringNavigation() when navigation starts to open a short retention window, then call its returned function after the destination appears:import { useRef } from 'react';
import { preserveFocusedInteractableDuringNavigation } from '@wearables-ui-toolkit/mrbd';
export function useFocusRetainingNavigation(navigateTo: (path: string) => void) {
const releaseRef = useRef<(() => void) | null>(null);
return {
navigate(path: string) {
releaseRef.current?.();
releaseRef.current = preserveFocusedInteractableDuringNavigation();
navigateTo(path);
},
settle() {
releaseRef.current?.();
releaseRef.current = null;
},
};
}
settle() after the destination arrives; PageTransition’s onTransitionEnd is a suitable point. Avoid releasing in the navigation tick, because the browser has not yet moved focus and the window would close too soon. The window expires automatically if the application misses a release, while an explicit release prevents retention from outliving the transition.@wearables-ui-toolkit/mrbd/react-router entry point to connect these primitives to React Router. This entry point requires react-router-dom as an optional peer dependency. Install it in applications that import the adapter; the main toolkit entry point does not include it.npm install react-router-dom
| Export | Kind | Purpose |
|---|---|---|
ReactRouterNavigationProvider | component | Registers Back handling, repairs history, and provides the preload queue. |
ReactRouterPageTransition | component | Derives the PageTransition key and direction from the router. |
ReactRouterPreloadLink | component | Renders a Link that warms its destination. |
useReactRouterNavigation | hook | Reads { navigateBack } from the provider. |
useReactRouterPageTransition | hook | Computes transition state without rendering the component. |
getBrowserHistoryIndex | function | Reads the router history index from browser history state. |
getPageTransitionDirectionForHistory | function | Maps history positions and navigation type to a direction. |
ReactRouterNavigation, ReactRouterNavigationProviderProps, ReactRouterNavigationType, ReactRouterPageTransitionChildren, ReactRouterPageTransitionProps, ReactRouterPageTransitionRender, ReactRouterPageTransitionState, ReactRouterPreloadLinkProps, and UseReactRouterPageTransitionOptions.ReactRouterNavigationProvider inside the router and around the routed application.| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | none | Supplies the routed application. |
fallbackTo | To \| null | '/' | Selects the Back destination at the first history entry. null disables fallback navigation. |
getHistoryIndex | () => number \| null | getBrowserHistoryIndex | Supplies the current history position. |
preloadRoute | RoutePreloadHandler | none | Handles the internal RoutePreloadProvider queue. |
navigateBack() calls use navigate(-1) when the index is greater than zero. At the first entry, or when the index is unavailable, the provider navigates to fallbackTo with replace and marks the transition as back, keeping motion aligned with the user’s navigation intent. It returns false and leaves desktop Escape unclaimed when fallbackTo is null or the current location already matches the fallback.pushState to replaceState, keeping each router push reachable through system Back.useReactRouterNavigation() returns ReactRouterNavigation, which is { navigateBack }, and throws when no provider is present. Use this hook when a control needs the same application-level Back behavior.ReactRouterPageTransitionReactRouterPageTransition accepts all PageTransition props except children, direction, and transitionKey, which it derives. It also accepts the following options:| Prop | Type | Default | Description |
|---|---|---|---|
getTransitionKey | (location: Location) => string | location => location.pathname | Identifies the current page. |
getHistoryIndex | () => number \| null | the provider’s, then getBrowserHistoryIndex | Supplies the current history position. |
children | ReactRouterPageTransitionChildren | required | Supplies route content or a render function receiving transition state. |
import { ReactRouterPageTransition } from '@wearables-ui-toolkit/mrbd/react-router';
<ReactRouterPageTransition>
{state => <Routes location={state.location}>{routeElements}</Routes>}
</ReactRouterPageTransition>
ReactRouterPageTransitionState contains direction, historyIndex, location, navigationType ('POP' | 'PUSH' | 'REPLACE'), and transitionKey. Pass state.location to Routes so back navigation renders the destination page instead of resolving against the new location too early.useReactRouterPageTransition(options?) returns the same state for applications that render PageTransition directly. It remembers the committed direction for each key, preventing callback identity changes during a re-render from reversing an active transition.getPageTransitionDirectionForHistory(previousHistoryIndex, currentHistoryIndex, navigationType) returns 'back' only for a 'POP' navigation with a lower history index. A push, replace, forward pop, or missing index returns 'forward'. getBrowserHistoryIndex() reads this index from browser history state and returns null when unavailable, which falls back cleanly to forward motion.ReactRouterPreloadLinkReactRouterPreloadLink renders a Link that also acts as a preload target. It accepts every Link prop and these additions:| Prop | Type | Default | Description |
|---|---|---|---|
preloadKey | string \| null | the resolved href | Supplies the key to preloadRoute. |
preloadOnFocus | boolean | true | Warms when the link receives focus. |
preloadOnPointerEnter | boolean | true | Warms on pointer entry. |
preloadOnVisible | boolean | true | Warms when the link becomes visible. |
preloadRoot | Element \| null | nearest scroll container | Sets the observation root. |
preloadRootMargin | string | no margin | Sets the observation root margin. |
preloadThreshold | IntersectionObserverInit['threshold'] | 0.01 | Sets the visibility threshold. |
preloadKey defaults to the resolved href, a preload handler keyed by pathname works without additional link configuration. Set preloadKey={null} to disable warming for one link while leaving the provider active. The component forwards its ref to the underlying anchor.import { Suspense } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { App, createPreloadableLazyComponent } from '@wearables-ui-toolkit/mrbd';
import {
ReactRouterNavigationProvider,
ReactRouterPageTransition,
} from '@wearables-ui-toolkit/mrbd/react-router';
const HomeRoute = createPreloadableLazyComponent(
() => import('./routes/HomeRoute'),
module => module.HomeRoute,
);
const SettingsRoute = createPreloadableLazyComponent(
() => import('./routes/SettingsRoute'),
module => module.SettingsRoute,
);
const preloadableRoutes = {
'/': HomeRoute,
'/settings': SettingsRoute,
};
function preloadRoute(preloadKey: string): Promise<void> | void {
const route = preloadableRoutes[preloadKey as keyof typeof preloadableRoutes];
return route?.preload();
}
export function ExampleApp() {
return (
<App>
<BrowserRouter>
<ReactRouterNavigationProvider preloadRoute={preloadRoute}>
<ReactRouterPageTransition>
{state => (
<Suspense fallback={null}>
<Routes location={state.location}>
<Route path="/" element={<HomeRoute.Component />} />
<Route
path="/settings"
element={<SettingsRoute.Component />}
/>
</Routes>
</Suspense>
)}
</ReactRouterPageTransition>
</ReactRouterNavigationProvider>
</BrowserRouter>
</App>
);
}
import { Button, ListItem, Page, RoutePreloadTarget } from '@wearables-ui-toolkit/mrbd';
import { ReactRouterPreloadLink } from '@wearables-ui-toolkit/mrbd/react-router';
import { useNavigate } from 'react-router-dom';
export function HomeRoute() {
const navigate = useNavigate();
return (
<Page showHeader headerText="Home">
<ReactRouterPreloadLink to="/settings">Settings</ReactRouterPreloadLink>
<RoutePreloadTarget preloadKey="/settings">
<Button title="Settings" onActivate={() => navigate('/settings')} />
</RoutePreloadTarget>
<ListItem title="Recent" subtitle="Nothing new" />
</Page>
);
}
RoutePreloadTarget adds a plain, non-focusable wrapper. Wrapping an interactive component therefore does not create a nested focus target.replace for genuine redirects so Back remains predictable.