Skip to content

Patterns · Layout

SplitInboxLayout

The one-page inbox: list, detail and context panes, resizable and collapsible, single-pane with back navigation on phones.

@rookcom/monarch-react/patternsPattern

Inbox with list, conversation and details

Printer offline on floor 3

Open
From Grace Hopper

It stopped after the update this morning.

Usage examples

The example at the top of the page is the only one for this component.

Purpose

Lays out a whole inbox on one page: a list pane, a detail pane and an optional context pane (requester, properties, activity). Panes resize with the pointer and the keyboard and collapse; below 768px it becomes one pane with a back button, and below 1280px the context pane becomes a drawer. The product supplies every pane’s content and owns the selection and URL state. One powerful page instead of five similar ones.

Use when

  • A queue of items people work through one by one: tickets, conversations, applications, review requests.
  • The list, the open item and its context should stay on screen together.

Avoid when

  • A plain list-and-detail without resizing or a context pane: use SplitLayout.
  • A table people scan and sort: use DataTable on its own page.

Design principles

  • Content-agnostic: the layout never fetches, routes or selects; it shows what it is given.
  • Every state is a prop: detailOpen, listCollapsed, contextOpen and the sizes can live in the URL or storage, with change callbacks.
  • The same layout from 320px to wide screens: one pane, two panes, three panes.
  • Panes are landmarks, and focus moves with what the user is looking at.

API reference

import { SplitInboxContextToggle, SplitInboxLayout, useSplitInboxLayout } from '@rookcom/monarch-react/patterns';
ExportKindRenderingRoot class
SplitInboxContextTogglecomponentclient.mn-split-inbox
SplitInboxLayoutcomponentclient.mn-split-inbox
useSplitInboxLayouthookclient–

Renders a <div> with the list (<aside>), the detail (<section>) and the context (<aside>, or a Sheet below 1280px). Forwards its ref.

The splitters are APG window splitters: role="separator" with aria-valuenow (px), aria-valuemin, aria-valuemax and aria-controls.

SplitInboxContextToggle is the details button for the detail pane’s header: it toggles the column on wide screens and the drawer otherwise, with aria-expanded.

useSplitInboxLayout() returns { layout, contextMode, contextVisible, setContextVisible, toggleContext, listCollapsed, setListCollapsed, focusPane, ids } inside a pane (or null), for custom toggles such as a focus-mode button.

Breakpoints are fixed (768px and 1280px) and shared by the stylesheet and the component, so the first render matches the server.

Exported from @rookcom/monarch-react/patterns.

Generated from the TypeScript declarations of packages/react/src/patterns/layout/split-inbox-layout.

Properties

SplitInboxContextToggle

The button that shows and hides the context pane, for the detail pane's header. It toggles the column on wide screens and the drawer otherwise, and exposes the state with `aria-expanded`.

PropTypeDescription
asChildbooleanRender the child element (e.g. a Next.js <Link>) with button styling.
brandColorstringBrand or tenant colour for `variant="brand"`, as `#rrggbb` or `#rgb`. The fill and text are derived per theme by the tokens contrast engine, so the label always meets WCAG AA (7:1 in more-contrast themes); the colour may be darkened or lightened to get there. Ignored by other variants. An invalid or missing colour falls back to the product accent.
classNamestring
disabledboolean
hideLabelstringLabel while it is showing. Default "Hide details".
iconReactNodeReplaces the default panel icon. Decorative.
idstring
loadingbooleanShows a spinner, sets aria-busy and blocks clicks while keeping focus.
showLabelstringLabel while the context pane is hidden. Default "Show details".
sizeButtonSize
variantButtonVariantVisual weight. Use one `primary` per view region. Default `secondary`.

SplitInboxLayout

The one-page inbox: list, detail and an optional context pane. Panes are resizable with the pointer and the keyboard and collapsible; below 768px it becomes one pane with back navigation, and below 1280px the context pane becomes a drawer. F6 and Shift+F6 move focus between panes. The product supplies every pane's content and owns the URL state.

PropTypeDescription
detail requiredReactNodeThe detail pane: the open item, or an EmptyState when nothing is selected.
list requiredReactNodeThe list pane: queue switcher, search, the conversation list.
backLabelstringLabel of that back button. Default "Back to list".
childrenReactNode
classNamestring
contextReactNodeThe optional context pane: requester, properties, activity.
contextCloseLabelstringAccessible name of the drawer's close button. Default "Close details".
contextLabelstringName of the context pane landmark and the drawer title. Default "Details".
contextOpenbooleanWhether the context column shows on wide screens (controlled). Below 1280px the context pane is a drawer with its own open state, closed at first, so a remembered "open" never opens a modal on page load.
contextResizeLabelstringDefault "Resize details".
contextSizenumberWidth of the context column in px (controlled).
defaultContextOpenbooleanDefault true.
defaultContextSizenumberInitial context width in px. Default 340.
defaultListCollapsedboolean
defaultListSizenumberInitial list width in px. Default 360.
detailLabelstringName of the detail pane landmark. Default "Detail".
detailOpenbooleanBelow 768px only one pane shows: the list, or the detail when this is true. Keep it in the URL (an open item id) so Back works.
idstring
listCollapsedbooleanHides the list on wide screens: focus or full-page mode (controlled).
listLabelstringName of the list pane landmark. Default "List".
listResizeLabelstringDefault "Resize list".
listSizenumberWidth of the list pane in px (controlled).
maxContextSizenumberDefault 520.
maxListSizenumberDefault 560.
minContextSizenumberDefault 260.
minListSizenumberDefault 240.
onBack(() => void)Renders a back button at the top of the detail pane below 768px.
onContextOpenChange((open: boolean) => void)
onContextSizeChange((size: number) => void)
onListCollapsedChange((collapsed: boolean) => void)
onListSizeChange((size: number) => void)Called while the list is resized. Persist it to remember the width.
resizablebooleanShow the keyboard and pointer splitters between panes. Default true.
resizeStepnumberPixels per arrow key press (Shift: four times). Default 16.

Variants

PropValuesDescription
context'omitted''provided'Two panes, or three with the context pane and its splitter.
resizable'true''false'Shows the splitters. Without them panes keep their sizes.
layout (from the viewport)'split (768px and up)''single (below 768px)'Exposed as data-layout. In single, detailOpen picks the pane.

States

StateExposed asBehaviour
Detail opendata-detail-openBelow 768px the detail pane replaces the list.
List collapsed (focus mode)data-list-collapsedOn wide screens the list is hidden; its splitter stays as a thin handle that expands it.
Context opendata-context-openThe context column shows (1280px and up).
Context drawer–Below 1280px the context opens as a modal Sheet; it starts closed so a remembered "open" never opens a modal on load.

Interaction behaviour

  • Drag a splitter to resize its pane, clamped to the min and max; sizes are reported through onListSizeChange and onContextSizeChange.
  • Clicking the collapsed list handle expands the list.
  • Below 768px, opening an item (detailOpen) moves focus to the detail pane; going back returns focus to the element that opened it.
  • Closing the drawer returns focus to the button that opened it.

Keyboard support

KeysAction
F6 / Shift+F6Moves focus to the next or previous visible pane, back to the element focused there last.
ArrowLeft / ArrowRightOn a splitter, moves it by resizeStep (16px); with Shift, four times as far.
Home / EndOn a splitter, sets its pane to the minimum or maximum size.
EnterOn the list splitter, collapses or expands the list; on the context splitter, closes the context pane.
EscapeCloses the context drawer.

Accessibility

Role
complementary (list), region (detail), complementary (context) landmarks
Accessible name
listLabel, detailLabel and contextLabel.
  • Each splitter is focusable and named (listResizeLabel, contextResizeLabel) with its value in pixels.
  • Panes are programmatic focus targets (tabindex="-1") with a visible focus ring when reached by F6.
  • The drawer is a modal dialog titled by contextLabel with a named close button; focus is trapped inside it.
  • Arrow keys follow the reading direction in right-to-left layouts.

Responsive behaviour

  • 1280px and up: list, detail and context side by side.
  • 768px to 1279px: list and detail; the context pane is a drawer.
  • Below 768px: one pane at a time with a back button in the detail pane; splitters are hidden.
  • The layout fills its container (height: 100%); give the container a height.

Theme support

  • Pane surfaces, borders and the splitter highlight use Monarch tokens, so every theme applies.
  • In forced-colours mode the splitter highlight uses the system Highlight colour.
  • The splitter highlight transition is removed with reduced motion.

Do and don't

Do

  • Keep the open item, focus mode and pane sizes in the URL or storage, and pass them in.
  • Give the container a fixed height (the viewport minus the app header).
  • Name the panes after their content: "Conversations", "Conversation", "Ticket details".

Don't

  • Do not build separate pages for the list and the item; this is the one page.
  • Do not move focus into the detail pane yourself on wide screens; the user may still be in the list.
  • Do not put a second SplitInboxLayout inside a pane.
  • SplitLayout: A simpler two-pane list and detail.
  • RecordHeader: The header of the open item in the detail pane.
  • Sheet: The drawer the context pane becomes on narrower screens.
  • EmptyState: The detail pane before anything is selected.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsButton, Sheet

Testing coverage

  • Renders the panes as labelled landmarks with two splitters; ref forwarding.
  • List splitter: arrows, Shift+arrows, Home/End and clamping; Enter collapses and an arrow expands (uncontrolled).
  • Controlled sizes change only through the parent.
  • Pointer drag resizes both panes in the right direction, clamped; moves after release are ignored.
  • Context splitter: ArrowLeft widens; Enter closes the pane and moves focus to the detail.
  • SplitInboxContextToggle toggles the context column with aria-expanded and aria-controls.
  • Below 1280px the context is a modal drawer, closed at first; Escape closes it and returns focus to the toggle.
  • Below 768px one pane shows; opening moves focus to the detail and Back returns it to the opener.
  • F6 and Shift+F6 cycle the visible panes, returning to the last focused element.
  • A collapsed list is skipped by F6; useSplitInboxLayout exposes the state and is null outside.
  • Renders on the server in the wide layout.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/split-inbox-layout; Cross-browser axe: /components/split-inbox-layout

Tests: packages/react/src/patterns/layout/split-inbox-layout