PageTransition to animate changes between routes. Provide a transitionKey that identifies the current page and render that page as children. While the key remains stable, the host forwards updated children to the mounted page. When the key changes, the host keeps the outgoing page mounted, animates the outgoing and incoming pages, and then removes the outgoing page.PageTransition is router-agnostic: it does not read history or import a router. The application or routing library owns the transition key and direction. With React Router, prefer ReactRouterPageTransition, which connects that routing state for the application.import type { ReactNode } from 'react';
import { PageTransition } from '@wearables-ui-toolkit/mrbd';
import type { PageTransitionDirection } from '@wearables-ui-toolkit/mrbd';
type RouteHostProps = {
content: ReactNode;
direction: PageTransitionDirection;
routeKey: string;
};
export function RouteHost({ content, direction, routeKey }: RouteHostProps) {
return (
<PageTransition transitionKey={routeKey} direction={direction}>
{content}
</PageTransition>
);
}
import {
AppSwitchPageTransitionConfig,
PageTransition,
} from '@wearables-ui-toolkit/mrbd';
<PageTransition
transitionKey={surface.id}
config={AppSwitchPageTransitionConfig}
>
{surface.content}
</PageTransition>;
import { PageTransition } from '@wearables-ui-toolkit/mrbd';
<PageTransition
transitionKey={routeKey}
initialFocus={{ selector: '[data-primary-action]' }}
>
{content}
</PageTransition>;
PageTransitionProps. It extends standard div attributes except children and onTransitionEnd, which it redeclares. The component forwards all other attributes to the viewport element.| Prop | Type | Default | Description |
|---|---|---|---|
transitionKey | string | — | Required. Identifies the current page. A change starts a transition. |
children | ReactNode | — | Required. Content of the current page. |
config | PageTransitionConfig | DefaultPageTransitionConfig | Defines the enter and exit motion. |
direction | PageTransitionDirection | 'forward' | Selects config.forward or config.back. |
disabled | boolean | false | Replaces the page immediately without animation. onTransitionEnd still fires. |
initialFocus | PageTransitionInitialFocus | 'top-left' | Selects focus for a page with no saved state. The restoration pass resolves it, so it has no effect when preservePageState is off. |
preservePageState | boolean | true | Captures and restores scroll offsets and the focused control for each key. |
timingScale | number \| 'auto' | 'auto' | Multiplies every duration, delay, and settle time. |
onTransitionEnd | () => void | — | Called after the outgoing page is removed. |
className | string | '' | Class added to the viewport element. |
pageClassName | string | '' | Class added to each page layer. |
style | CSSProperties | — | Inline styles merged over the generated transition custom properties. |
onFocusCapture, onMouseDownCapture, and onPointerDownCapture handlers still run.PageTransitionDirection is 'forward' | 'back'.PageTransitionInitialFocus supports four forms:| Value | Behavior |
|---|---|
'top-left' | Default. Focuses the focusable 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 within the page. |
(pageElement: HTMLElement) => HTMLElement \| null | Lets the caller resolve the target. |
| Config | Use |
|---|---|
InAppPageTransitionConfig | Route changes within one application. It defines both directions with a short horizontal slide crossed with a fade and no scaling. |
AppSwitchPageTransitionConfig | Switches between applications or top-level surfaces. It is longer and larger, and adds scale so the outgoing surface recedes. Forward only. |
DefaultPageTransitionConfig | Alias for InAppPageTransitionConfig and the default when config is omitted. |
PageTransitionConfig, PageTransitionMotionConfig, PageTransitionStateConfig, PageTransitionValueSegmentConfig, and PageTransitionSegmentConfig describe a config. Each of alpha, scale, and translateX animates as a separate segment with its own delay, duration, and easing. Separate segments can, for example, start a page’s slide before its fade. totalDurationMs is the settle time for the full transition, not the sum of its segments. When direction is 'back' and the config has no back entry, the component uses its forward motion.totalDurationMs, adjusted by the resolved timingScale, the host unmounts the outgoing page and calls onTransitionEnd.children during a transition updates only the incoming page; the outgoing page retains the content it had when the key changed. Another transitionKey change restarts the transition toward the newest key. Timing-related prop changes do not restart an active transition, which finishes with its original timings.timingScale. The component clamps a numeric value between one quarter and four times the configured timings. A value that is not finite and positive falls back to 1. The default 'auto' uses the configured timings in an ordinary browser and extends them in embedded browser environments where the compositor needs a longer window to produce the same perceived motion.preservePageState on, the host captures the scroll offset of every scrollable element and the control that had focus when a page is left. It stores one latest snapshot per transitionKey and restores both before a returning page becomes visible. Two browser-history entries that use the same key therefore share that snapshot; use a more specific key when they require independent restoration. This is not general application state persistence: store route data, form models, selections, and pager or carousel indices separately. The host also synchronizes the control’s focus state so it displays focused material instead of only a browser outline. The capture accounts for timing races: if a click triggers navigation before focus moves, it restores the clicked control; if the browser has already dropped focus, it uses the page’s most recently focused control.aria-disabled="true" are excluded from the saved focus order and from initial focus, even though a disabled InteractableBase remains reachable through ordinary directional navigation. Prefer stable page structure and focus order across visits. When either must change, consider turning preservePageState off and letting the page own focus placement.initialFocus applies only when the current key has no saved snapshot. The resolved element must be focusable and eligible. The host skips a control with initialFocusEligible={false}. If a selector or function resolves to an ineligible element, focus remains unchanged. When focusable content loads lazily, the host retries on the next render, so a Suspense fallback does not prevent initial focus.prefers-reduced-motion: reduce is active, the shipped stylesheet reduces enter and exit animations to a negligible duration. Pages therefore change without sliding or fading. The host still retains the outgoing page for the configured settle time, so onTransitionEnd timing does not change.aria-hidden and inert. This is required so only the incoming page can receive focus, pointer input, or assistive-technology navigation.Page, so assistive technology receives the correct structure.initialFocus, before revealing the page. This prevents screen readers from landing at the document start instead of the intended control.direction from the navigation semantics rather than the trigger’s screen position. A route that animates back while adding a history entry communicates the wrong relationship to the user.transitionKey. Prefer sharing a key between two states of the same page so they update without animating against each other.direction from history position or navigation semantics to keep the motion consistent with the route relationship.preservePageState behavior over separate scroll and focus restoration. Store route data, form models, selections, and pager or carousel indices in application or router state.PageTransition for the same route level because each host would compete to own the route transition and page state.initialFocus only with preservePageState; the same restoration pass resolves it.