Skip to content

ListDetailLayout

ListDetailLayout pairs a master list with a detail pane: CRM contacts to a record, research runs to a report, conversations to a thread. It’s responsive to the layout’s own width, side by side when wide, and one at a time when narrow, showing the list or the selected detail with a back affordance to return.

import { ListDetailLayout } from "@nimblebrain/synapse/ui";
<ListDetailLayout selected={selected !== null} onBack={() => setSelected(null)}>
<ListDetailLayout.List>{/* rows */}</ListDetailLayout.List>
<ListDetailLayout.Detail>
<ListDetailLayout.Back />
{/* detail */}
</ListDetailLayout.Detail>
</ListDetailLayout>;
  • ListDetailLayout.List: the master list. On a narrow layout it’s hidden once something is selected, handing the stage to the detail.
  • ListDetailLayout.Detail: the detail pane.
  • ListDetailLayout.Back: a back link placed at the top of Detail; it renders only on a narrow layout and calls onBack.

Each pane runs edge to edge (its scrollbar sits on the frame’s edge or the divider) and insets its own content by the gutter: the detail by the gutter, the list by the gutter less a ListRow’s own padding, so row text lands on the line your page’s title and tabs sit on. Other content in the list, such as a SearchField above the rows, sits a row’s padding (0.75rem) inside that line. Put the layout in AppFrame.Body bleed, so nothing around it adds a second inset. Pass inset={false} to a pane whose content runs edge to edge itself.

selected is driven by your own selection state. Read the responsive state with the useListDetail() hook for custom back affordances.

Prop Type Default
selected boolean false
onBack () => void None
listWidth number | string 320
breakpoint number 720

ListDetailLayout.List and ListDetailLayout.Detail

Section titled “ListDetailLayout.List and ListDetailLayout.Detail”
Prop Type Default
inset boolean true
  • ListRow: the rows that fill the master list.
  • Drawer: an overlay alternative when the detail shouldn’t take a permanent column.