Source: https://monarchui.dev/components/split-layout/

Patterns · Layout

# SplitLayout

List and detail panes side by side, one pane at a time on narrow screens.

@rookcom/monarch-react

Pattern

## Inbox

## Usage examples

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

## Purpose

Two-pane list + detail layout for inbox-style views. On wide screens both panes show; below 768px only one shows, chosen by `detailOpen`, with an optional back button.

### Use when

- Browsing a list while reading one item (an inbox, a chat list, a document list).

### Avoid when

- Detail that needs the full page: navigate to a detail page instead.

## Design principles

- No routing inside: the app decides what is open and passes `detailOpen`.
- Each pane is a labelled region so screen-reader users can jump between them.
- Each pane scrolls on its own.

## API reference

```tsx
import { SplitLayout } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `SplitLayout` | component | client | `.mn-split` |

Renders a div with two `<section>` panes; native div attributes pass through. No ref is forwarded.

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

## Properties

### `SplitLayout`

List + detail two-pane layout (Tickets inbox, Chat, Dossier). No routing inside.

| Prop | Type | Description |
| --- | --- | --- |
| `detail` required | `ReactNode` | The detail pane. When empty, pass an EmptyState. |
| `list` required | `ReactNode` | The list pane (e.g. ticket list). |
| `backLabel` | `string` |  |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `detailLabel` | `string` |  |
| `detailOpen` | `boolean` | Below the `md` breakpoint only one pane shows. `detailOpen` picks which; `onBack` renders a back button in the detail pane. |
| `id` | `string` |  |
| `listLabel` | `string` |  |
| `listWidth` | `"sm" \| "md" \| "lg"` | Width of the list pane on wide screens. |
| `onBack` | `(() => void)` |  |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `listWidth` | `'sm'``'md'``'lg'` | List pane width on wide screens: 18rem, 22rem (default) or 28rem. Exposed as `data-list-width`. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Detail open | `data-detail-open` | Below 768px, shows the detail pane instead of the list. |

## Interaction behaviour

- With `onBack`, the detail pane renders an IconButton (label `backLabel`, default "Back to list") that calls it.
- The back button is hidden from 768px up.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Moves through list content, then detail content (including the back button). |

## Accessibility

Role

region (two labelled sections)

Accessible name

listLabel

(default "List") and

detailLabel

(default "Detail").

- Give both panes specific labels, for example "Tickets" and "Ticket".
- Focus is not moved when `detailOpen` changes; the app should move it to the opened detail.

## Responsive behaviour

- Below 768px: one column; the list shows unless `detailOpen`, then the detail shows.
- From 768px: list column at `listWidth`, detail fills the rest.
- The layout fills its container height (`height: 100%`); give the container a height.

## Theme support

- Pane divider and back-bar borders use Monarch border tokens.

## Do and don't

### Do

- Show an EmptyState in the detail pane when nothing is selected.
- Pass `onBack` so narrow screens can return to the list.

### Don't

- Do not nest SplitLayouts.
- Do not leave the container without a height.

## Related components

- [SplitInboxLayout](https://monarchui.dev/components/split-inbox-layout/): Planned three-pane inbox template (wave 8).
- [EmptyState](https://monarchui.dev/components/empty-state/): Placeholder for an empty detail pane.
- [PageHeader](https://monarchui.dev/components/page-header/): Sits above the layout.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | None |
| Monarch components | [Button](https://monarchui.dev/components/button/) |

## Testing coverage

- Both panes are labelled regions.
- The back button calls onBack.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | None yet |

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

## All example code

### Inbox

```tsx
import { useState } from 'react';
import { Button, EmptyState, SplitLayout } from '@rookcom/monarch-react';

function Inbox() {
  const [open, setOpen] = useState<string | null>(null);
  return (
    <div style={{ height: '16rem', width: '100%' }}>
      <SplitLayout
        listLabel="Conversations"
        detailLabel="Conversation"
        detailOpen={open !== null}
        onBack={() => setOpen(null)}
        list={
          <div style={{ display: 'grid', gap: '0.5rem', padding: '0.5rem' }}>
            {['Billing question', 'Password reset'].map((t) => (
              <Button key={t} variant="ghost" onClick={() => setOpen(t)}>
                {t}
              </Button>
            ))}
          </div>
        }
        detail={
          open ? (
            <p style={{ padding: '1rem' }}>{open}</p>
          ) : (
            <EmptyState title="No conversation selected" description="Pick one from the list." />
          )
        }
      />
    </div>
  );
}

<Inbox />
```
