Skip to content

FocusLayout

<FocusLayout> is the wizard / onboarding shell — close button + breadcrumb top-left, optional stepper below, body in the middle, sticky action bar at the bottom. No sidebar, no app chrome — distraction-free.

import from "@na/ui/components/FocusLayout" ▶ Open in Storybook packages/ui/src/components/FocusLayout.tsx

When to use

  • “Create agent” wizards.
  • Onboarding flows that the user starts and finishes in one session.
  • Any flow where the app sidebar would distract from the task.

Don’t use FocusLayout for: detail pages with tabs (DetailPageBar is correct), settings sub-pages, or modals (use Dialog).

Default

  1. Basics
  2. Tools
  3. Review

Step 1 body…

<FocusLayout
breadcrumb={['Agents', 'Create']}
stepper={{ current: step, steps: ['Basics', 'Tools', 'Review'] }}
onClose={() => router.push('/agents')}
actions={
<>
<Button variant="outline" disabled={step === 1} onClick={back}>Back</Button>
<Button onClick={next}>{isLast ? 'Submit' : 'Next'}</Button>
</>
}
>
{/* step body */}
</FocusLayout>

API

Prop Type Default Description
closeTo string URL the ✕ button navigates to. Ignored when onClose / closeAction is set.
onClose () => void Preferred for SPA routers. Called by ✕ and Esc.
closeAction ReactNode Replace the default ✕ with a custom node — typically a ConfirmPopover for confirm-on-discard.
breadcrumb ReactNode[] Optional breadcrumb shown next to the ✕.
stepper { current, steps, orientation? } Stepper config. Renders below the close + breadcrumb row.
actions ReactNode Sticky bottom action-bar content. Right-aligned cluster.
closeLabel string "Close" Close-button aria-label.
ariaLabel string Accessible title — defaults to last breadcrumb entry.
children * ReactNode Body content for the active step.

Design guidelines

✓ Do

  • Use closeAction with a ConfirmPopover when the flow has unsaved state ("Discard changes?").
  • Cap steps at 5. Past that, the user can't hold the path in their head.
  • Place primary action on the right of the action bar; Back / Cancel on the left.

✗ Don't

  • Add a sidebar inside FocusLayout. The whole point is to hide app chrome.
  • Allow forward jumps to unvisited steps. The Stepper inside FocusLayout is intentionally linear.
  • Use FocusLayout for short single-step forms. PageHeader + PageContent is enough.

Accessibility

  • Esc triggers onClose (or follows closeTo).
  • Focus ring on the close button; first focusable item in the body receives initial focus.
  • Stepper exposes aria-current="step" on the active step.
  • Stepper — Free-standing stepper for in-page wizards.
  • Dialog — Modal flavor for shorter “create” flows.
  • ConfirmPopover — For closeAction confirm-on-discard.

▶ Open FocusLayout stories in Storybook