Application structure

Updated: Oct 10, 2026
Structure a Meta Ray-Ban Display web application as three layers with one clear owner at each level. This separation keeps routing, focus, and layout behavior predictable.
LayerRenderedOwns
Shell
once, at the entry point
App, the router, the page transition
Route surface
normally one primary surface per route
Page or SubNavigationPager
Regions
inside the route surface
task-appropriate content and an optional docked action row; one owner per scrolling axis when needed

The shell

Give the mount element a size because App fills its parent. Use the semantic window-background role for the document instead of a literal color so the additive-light display leaves background pixels unlit.
html,
body,
#root {
  block-size: 100%;
  background-color: var(--uit-color-background-window);
}
Render App once around the entire application. It installs the font faces, focus-navigation context with automatic initial focus, portal root for floating overlays, toast host, and theme scope that provides the window background and text color. These services work together, so do not build a partial shell that leaves overlays, toasts, or directional navigation without their required context.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { App } from '@wearables-ui-toolkit/mrbd';
import {
  ReactRouterNavigationProvider,
  ReactRouterPageTransition,
} from '@wearables-ui-toolkit/mrbd/react-router';
import { InboxRoute } from './routes/InboxRoute';
import { LibraryRoute } from './routes/LibraryRoute';
import { SettingsRoute } from './routes/SettingsRoute';
import { ThreadRoute } from './routes/ThreadRoute';

function AppRoutes() {
  return (
    <ReactRouterNavigationProvider fallbackTo="/">
      <ReactRouterPageTransition>
        {({ location }) => (
          <Routes location={location}>
            <Route path="/" element={<InboxRoute />} />
            <Route path="/thread/:threadId" element={<ThreadRoute />} />
            <Route path="/library" element={<LibraryRoute />} />
            <Route path="/settings" element={<SettingsRoute />} />
          </Routes>
        )}
      </ReactRouterPageTransition>
    </ReactRouterNavigationProvider>
  );
}

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <BrowserRouter>
      <App>
        <AppRoutes />
      </App>
    </BrowserRouter>
  </StrictMode>,
);
Keep these three shell responsibilities intact:
  • ReactRouterNavigationProvider registers the application-level Back handler. It pops history and, at the first history entry, navigates to fallbackTo ('/' by default). It also owns route preloading, so provide preloadRoute here when destinations are preloaded, and holds the focused control’s visual state across route changes.
  • ReactRouterPageTransition supplies a render function with the location snapshot currently being animated. Pass that snapshot to Routes; otherwise, Back can render the first matching route instead of the retained outgoing page.
  • Each route component renders its own route root. The transition wrapper does not provide page chrome and must not be used as a route root.
ReactRouterPageTransition forwards every PageTransition prop except direction and transitionKey, which it derives from history, and children, which also accepts the render function shown above. The most commonly configured props are config (DefaultPageTransitionConfig, InAppPageTransitionConfig, or AppSwitchPageTransitionConfig), initialFocus ('top-left' by default), and preservePageState (true by default).
A Next.js App Router application uses the same shell with NextNavigationProvider and NextPageTransition from @wearables-ui-toolkit/mrbd/next. Because Next.js resolves a layout’s children from the current route, NextPageTransition renders each screen from the pathname it supplies. Any other router composes PageTransition, useBackNavigation, RoutePreloadProvider, and useNavigationFocusRetention directly. Route navigation shows both shells.
The adapter also adds two options. getTransitionKey defines when a location represents a different page. It defaults to the location pathname, so changing only the search string or hash re-renders in place without an animation. getHistoryIndex changes how the adapter reads history depth to choose a 'forward' or 'back' transition. Prefer the default unless the host maintains its own history stack.

A standard Page route

Use Page for a single-screen route. It pins a header at the top center and creates a content layer from the same top inset to the bottom edge. The header floats above that layer, allowing scrolling content to pass underneath it.
showHeader defaults to true and is the only setting that hides the header; an empty header string still renders the header region. enableSystemBarInset defaults to true and places the content layer below the display’s system-bar region without moving the header.
import { Page, TextStyle, TextView } from '@wearables-ui-toolkit/mrbd';

export function SettingsRoute() {
  return (
    <Page headerText="Settings" headerMetadata="3 items">
      <div className="route-body">
        <TextView as="h2" textStyle={TextStyle.HEADING2}>
          Display
        </TextView>
        <TextView textStyle={TextStyle.BODY2}>
          Brightness adapts to ambient light.
        </TextView>
      </div>
    </Page>
  );
}
.route-body {
  display: flex;
  flex-direction: column;
  gap: var(--uit-spacing-med-lg);
  block-size: 100%;
  padding-inline: var(--uit-spacing-large);
}
Apply the large inline inset to general content that is not a scroller, list, or rail. Keep scrollers, lists, and rails edge to edge because their children provide the appropriate inset.

A scrolling list route

Place a vertical sequence of ListItem rows in VerticalList. This is the required list host: it is a ScrollView, and its row container already applies the list inline inset. Render it directly in the page content without wrapper padding, and keep adjacent rows flush without an extra gap.
import { ListItem, Page, VerticalList } from '@wearables-ui-toolkit/mrbd';
import { useNavigate } from 'react-router-dom';

export function InboxRoute() {
  const navigate = useNavigate();

  return (
    <Page headerText="Inbox" headerMetadata="2 new">
      <VerticalList ariaLabel="Inbox threads">
        <ListItem
          title="Sam Rivera"
          subtitle="On my way"
          timestamp="9:41"
          onClick={() => navigate('/thread/sam')}
        />
        <ListItem
          title="Field Ops"
          subtitle="Route updated"
          timestamp="9:12"
          onClick={() => navigate('/thread/ops')}
        />
      </VerticalList>
    </Page>
  );
}
Long lists normally scroll beneath the overlaid page header. When the first row needs to begin below the header, reserve that space on the scroller rather than inserting a spacer. Set insetForHeader and pass the header height measured through the page handle.
import { useLayoutEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  ListItem,
  Page,
  VerticalList,
  type PageHandle,
} from '@wearables-ui-toolkit/mrbd';

export function LibraryRoute() {
  const navigate = useNavigate();
  const pageRef = useRef<PageHandle>(null);
  const [headerHeight, setHeaderHeight] = useState(0);

  useLayoutEffect(() => {
    setHeaderHeight(pageRef.current?.getHeaderHeight() ?? 0);
  }, []);

  return (
    <Page ref={pageRef} headerText="Library">
      <VerticalList insetForHeader headerHeight={headerHeight}>
        <ListItem
          title="Recent"
          subtitle="12 items"
          onClick={() => navigate('/library/recent')}
        />
        <ListItem
          title="Saved"
          subtitle="4 items"
          onClick={() => navigate('/library/saved')}
        />
      </VerticalList>
    </Page>
  );
}
getHeaderHeight() returns 0 while the header is hidden, so this approach also works with showHeader={false}.

A route with a docked bottom action rail

Dock actions below the content they affect and outside its scroller. A two-row grid gives the scroller a flexible row and the action rail an intrinsic row, so content remains visible and never passes behind its actions. ButtonRail owns horizontal scrolling and its overflow fades. Because it scrolls on a different axis, it can coexist with the route’s vertical scroller.
import { useParams } from 'react-router-dom';
import {
  Button,
  ButtonRail,
  ListItem,
  Page,
  VerticalList,
  type IconSource,
} from '@wearables-ui-toolkit/mrbd';
import { archiveThread, shareThread, useThread } from '../data/threads';

const replyIcon: IconSource = '/icons/reply.svg';
const archiveIcon: IconSource = '/icons/archive.svg';
const shareIcon: IconSource = '/icons/share.svg';

export function ThreadRoute() {
  const { threadId = '' } = useParams();
  const thread = useThread(threadId);

  return (
    <Page headerText={thread.title} headerMetadata={thread.status}>
      <div className="route-layout">
        <VerticalList className="route-scroller" ariaLabel="Messages">
          {thread.messages.map(message => (
            <ListItem
              key={message.id}
              title={message.sender}
              subtitle={message.body}
              timestamp={message.time}
            />
          ))}
        </VerticalList>
        <ButtonRail className="route-actions">
          <Button title="Reply" icon={replyIcon} onClick={thread.reply} />
          <Button
            title="Archive"
            icon={archiveIcon}
            onClick={() => archiveThread(threadId)}
          />
          <Button
            title="Share"
            icon={shareIcon}
            onClick={() => shareThread(threadId)}
          />
        </ButtonRail>
      </div>
    </Page>
  );
}
.route-layout {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  block-size: 100%;
  min-block-size: 0;
  min-inline-size: 0;
}

.route-scroller {
  min-block-size: 0;
  min-inline-size: 0;
}

.route-actions {
  min-inline-size: 0;
  padding-block-end: var(--uit-spacing-xsmall);
}
Each declaration supports the responsive relationship:
  • block-size: 100% uses the height already calculated by the page content layer, so the route does not need the display dimensions.
  • minmax(0, 1fr) on the scroll row and min-block-size: 0 on the scroller let that row shrink below its content height. Both are required; without them, the full list height can push the rail below the display.
  • auto preserves the action row’s intrinsic height as its buttons resolve.
  • The xsmall bottom inset is the action region’s only additional spacing.

A tabbed SubNavigationPager route

Prefer SubNavigationPager as the primary surface for a compact set of peer destinations. It is full bleed, draws its own tab bar at the top center, and fades in a scrim behind the focused tab bar. Avoid placing it inside Page when that would create duplicate top chrome or competing full-size layout owners.
Each items entry must have one corresponding child; a mismatch throws during render. A child can render any React content. Wrap it with PagerPage only when that pane needs lifecycle callbacks.
import {
  ListItem,
  PagerPage,
  SubNavigationPager,
  VerticalList,
  type IconSource,
  type SubNavigationItem,
} from '@wearables-ui-toolkit/mrbd';
import { markAlertsSeen } from '../data/alerts';

const homeIcon: IconSource = '/icons/home.svg';
const peopleIcon: IconSource = '/icons/people.svg';
const alertsIcon: IconSource = '/icons/alerts.svg';

const TABS: SubNavigationItem[] = [
  { label: 'Home', icon: homeIcon },
  { label: 'People', icon: peopleIcon },
  { label: 'Alerts', icon: alertsIcon },
];

export function HomeRoute() {
  return (
    <SubNavigationPager
      items={TABS}
      defaultPageIndex={0}
      homeIndex={0}
      ariaLabel="Home sections"
    >
      <VerticalList ariaLabel="Home">
        <ListItem title="Next stop" subtitle="Warehouse 4" />
      </VerticalList>
      <VerticalList ariaLabel="People">
        <ListItem title="Sam Rivera" subtitle="Available" />
      </VerticalList>
      <PagerPage onWillShowPage={markAlertsSeen}>
        <VerticalList ariaLabel="Alerts">
          <ListItem title="Battery low" subtitle="Charge soon" />
        </VerticalList>
      </PagerPage>
    </SubNavigationPager>
  );
}
Each pane has an independent content scope. A pane may be static or may contain a scroller, and sibling panes can each own a vertical scroller because only the current pane participates in focus and scrolling. If content scrolls, avoid a nested owner for the same axis. Set homeIndex to make Back return to the home tab before leaving the route. useBackButtonForHome defaults to true and must remain enabled for that behavior. Navigation and Focus explains focus movement between the tab bar and current pane.
defaultPageIndex creates an uncontrolled pager, which is typically appropriate when the route always opens on the same tab. To preserve the selected tab after leaving and returning, control the pager with currentPageIndex and onPageChange, and store that index outside the route so it survives the route lifecycle.

Application responsibilities

  • Use available space. Prefer flexible tracks, percentages, and intrinsic component sizes. Avoid fixed lengths, including values hidden inside min(), max(), clamp(), or a grid track, so routes respond to their container.
  • Use one scroll owner per axis when content scrolls. Static content needs no scroller. A nested same-axis scroller breaks focus-driven scrolling and the fading edges that communicate overflow.
  • Dock actions below their content. Keep them at the bottom and outside the scroller so content remains unobscured.
  • Choose one initial-focus target. It must be fully visible when the route settles so directional navigation starts from a clear, usable location.
  • Compose with the toolkit components. Avoid recreating components with custom rounded rectangles, styling their internals, or relying on their DOM structure. The toolkit owns those implementation details and interaction behavior.
  • Keep material owners unnested. Do not nest Container, StaticContainer, Surface, Button, Chip, Tag, or the page Header inside another member of that set; nesting duplicates material, corner radius, and inset.
  • Store route-return state above the route. A pager index or selection that must survive Back belongs in a store that outlives the detail route.