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

PartRole
App.RootViewport-bound shell; owns responsive behavior, panel visibility, and size persistence
App.TriggerToggles 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.ExpandWidens an area to its maxSize and back to defaultSize. Desktop only, and only while the area is visible; returns null otherwise
App.DismissDismiss button for a mobile panel. Renders only when area is the active mobile view; returns null on desktop. Focus restoration is automatic

Hooks

HookRole
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

PropDescription
idRequired. Stable identifier used to namespace panel IDs and storage keys across reloads
storageOptional LayoutStorage adapter.
bodyRequired. The main content region
navigation, sidebarOptional resizable panel regions
footerOptional full-width region below the resizable panel group
bodyCanvas, navigationCanvas, sidebarCanvasOpt 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.

Sidebar

Demo sidebar content for the experimental App layout.