App
Installation
import { App, useDismiss } from "@o/pipeline/experimental/layouts/app";
Note
App.* components are React client components.
Usage
App.Root owns the full outer application shell. It renders drag-resizable navigation, body, and sidebar panels on desktop, with a single-region mobile layout below that. Panel sizes are persisted automatically.
<App.Root
id="my-app"
body={
<>
<App.Trigger
area="navigation"
render={<Button aria-label="Toggle navigation" />}
/>
<App.Trigger
area="sidebar"
render={<Button aria-label="Toggle sidebar" />}
/>
{/* Body content */}
</>
}
navigationCanvas
navigation={
<>
<App.Dismiss
area="navigation"
render={<Button aria-label="Close navigation" />}
/>
{/* Navigation content */}
</>
}
sidebarCanvas
sidebar={
<>
<App.Dismiss
area="sidebar"
render={<Button aria-label="Close sidebar" />}
/>
{/* Sidebar content */}
</>
}
/>
Parts
| Part | Role |
|---|---|
App.Root | Viewport-bound shell; owns responsive behavior, panel visibility, and size persistence |
App.Trigger | Toggles a panel area on desktop and the corresponding mobile region. Takes area="navigation" or area="sidebar"; returns null when the area’s slot is absent |
App.Expand | Widens an area to its maxSize and back to defaultSize. Desktop only, and only while the area is visible; returns null otherwise |
App.Dismiss | Dismiss button for a mobile panel. Renders only when area is the active mobile view; returns null on desktop. Focus restoration is automatic |
Hooks
| Hook | Role |
|---|---|
useDismiss(area) | Returns a callback that dismisses the matching active mobile view. It is a no-op on desktop or while another view is open |
App.Root
Props
| Prop | Description |
|---|---|
id | Required. Stable identifier used to namespace panel IDs and storage keys across reloads |
storage | Optional LayoutStorage adapter. |
body | Required. The main content region |
navigation, sidebar | Optional resizable panel regions |
footer | Optional full-width region below the resizable panel group |
bodyCanvas, navigationCanvas, sidebarCanvas | Opt a region into the inset “canvas” surface (gutter, bg-canvas, shadow-canvas, rounded corners) |
Navigation and sidebar visibility are internal session state, visible by default on desktop, and are not persisted. Only panel dimensions are saved, and only after direct user interaction.
App.Expand snaps an area between its resting width and its ceiling. Dragging a separator afterwards hands width control back to the user and clears the expanded state, so the next press expands again rather than restoring a width the user has already moved away from.
Triggers and dismiss use Pipeline’s render composition contract. Triggers expose aria-controls, aria-expanded, and data-state.
Persistence
App.Root persists panel sizes via useDefaultLayout from react-resizable-panels. Panel IDs are namespaced with the root id, and each rendered panel combination stores a separate layout.
For static or server-rendered apps, pass a custom LayoutStorage implementation — for example, a cookie-backed adapter that returns null without document:
import { type LayoutStorage } from "@o/pipeline/experimental/layouts/app";
const cookieStorage: LayoutStorage = {
getItem(key) {
if (typeof document === "undefined") return null;
// read and decode cookie by key
},
setItem(key, value) {
if (typeof document === "undefined") return;
// encode and write cookie
},
};
<App.Root id="my-app" storage={cookieStorage} ... />
[!NOTE ]
Per-user layouts restore during client hydration; they are not present in statically generated HTML.