Skip to content

Attio Layout Decision Guide

One-page cheat sheet. Open this before building a new screen. Before editing an existing one.

If a pattern below conflicts with something in 03-layout.md or 04-components.md, this guide is the newer source of truth (it informs the rewrite of those chapters). For deeper pattern anatomy, see attio-layout-patterns.md. For tokens, see attio-design-language.md.


The three questions

Before building, answer these in order. Each has one right answer.

  1. What am I displaying? → Many items / one item / a form / a workbench / a dashboard / a canvas. Pick the archetype.
  2. How should it look? → Inside the archetype, which representation? (Table vs kanban; split vs drawer; flat vs card.)
  3. How do the pieces compose? → Page bar + content + (optional) right panel + (optional) action bar. In that exact order.

If you’re unsure on Q1, stop and re-read the list of archetypes in 03-layout.md. Picking the wrong archetype is the root cause of every nested-scroll, every 9-tab detail page, every “where does the Save button go” PR comment.


Decision tree 1 — “I’m displaying N items”

┌─ Is there a status pipeline? ──── YES ─→ KANBAN
│ (open → in review → closed)
├─ Is time the primary axis? ────── YES ─→ CALENDAR
│ (due dates, scheduled events)
Multiple items ──────┤
├─ Is N < 20 AND each item ────── YES ─→ LIST
│ has a picture/avatar as identity? (flat list, larger rows)
└─ Otherwise ───────────────────────────→ TABLE
(default; 95% of cases)

Rules of thumb.

  • Default to Table. Attio’s first-class view; most dense; most-scannable.
  • Go to Kanban only when the status attribute is the reason the user is here. A list “that happens to have a status” is still a table.
  • Go to List (flat rows, richer per-row cell) only when the item visual matters more than the attributes. Tools and agents with custom icons → list-or-cards call, see §cards-vs-flat below.
  • Do not ship Gallery / Timeline / Map views unless a concrete user requirement forces it. Attio doesn’t have them; adding them multiplies maintenance.

Shipping multiple views? Rare. Most screens have one obviously-correct presentation — ship that and stop. A <ViewSwitcher> is only earned when all four of these are true:

  1. The screen is task-/flow-oriented — users triage, move items through stages, or work a queue (not “browse configuration”).
  2. Both representations are genuinely useful for the same user on the same data, not “one primary, one vanity.”
  3. The representations share the same query, filter, and sort state — switch is instant, not a refetch with different semantics.
  4. Users would realistically switch back and forth within a session, not pick once and forget.

If any of the four fails, pick the single best view and ship it. A settings-like list (agents registry, tools registry, versions) fails #1 — we know how to present it (a table), and nobody benefits from a kanban of their agents. Do not add a switcher.

Screens where the switcher does earn its keep: /review (inbox vs kanban by status), /agents/:id/tests (list vs kanban by pass/fail/pending), workflow run history (table vs pipeline view). These have real task/flow nature.


Settings is not a Detail-Split

The single most common misapplication: mechanically applying Attio’s 3a Split (main + right panel) to any screen that shows “one thing.” It’s wrong.

3a Split earns its keep only when the main area has genuine activity content — a timeline of emails/calls/notes/messages that scrolls down while the right panel holds static properties. If you’re writing configuration — name, description, model, prompt, variables, widget embed config, billing — the properties are the primary content. There’s nothing to put in the main area that isn’t just more properties.

For those, use Attio’s Settings archetype: <PageContent narrow> + flat <FormSection> groups + <Separator> between them. Full-width single column. No right panel. No card wrappers. See attio-design-language.md §3 “Settings / Configuration.”

Screen typeHas genuine activity?Archetype
Agent core settings (name, prompt, model, …)NoForm (Attio Settings)
Agent variablesNoForm (Attio Settings)
Widget configNoForm (Attio Settings)
Billing / security settingsNoForm (Attio Settings)
Knowledge-base doc viewerNo (doc is content, not timeline)Workbench (list-nav + viewer)
Tool settingsNoForm with list-nav
Conversation review (/review/:id)Yes (the conversation is the timeline)Detail 3a Split
CRM contact / company (future)Yes (emails/calls/notes)Detail 3a Split

Decision tree 2 — “I’m displaying one item”

┌─ Are the item's properties first-class YES ─→ SPLIT
│ alongside the tab content? (3a — main + right panel)
Single item ─────────┤
├─ Is each tab itself a workbench/canvas YES ─→ TABBED FULL-PAGE
│ (editor, chart, full-width dashboard)? (3b — no right panel)
└─ Does the user's primary context stay YES ─→ DRAWER
in the list (inbox, kanban triage)? (3c — overlay panel)
┌─ < 4 fields AND single-step commit? ────── YES ─→ MODAL (<Dialog>)
Form for one item ───┤
└─ Everything else ────────────────────────────────→ FORM PAGE
(PageContent narrow + ActionBar)

Rules.

  • Split (3a) is the default record detail. Right panel for properties + metadata, main area for activity / timeline / tabs.
  • Tabbed full-page (3b) is for content-heavy tabs. A workflow editor, a dense playground, a knowledge-base reader — each tab wants full width, so no right panel.
  • Drawer (3c) is for peek-without-leaving. Deep-link via ?record=<id>, Esc closes, “Open full page” action promotes to 3a.
  • Never put a modal form with > 4 fields. Use <Sheet> or a dedicated form page instead — same spec rule as before, unchanged.
  • Never nest a drawer inside a tabbed full-page. If the content is that deep, the archetype is wrong.

Decision tree 2.25 — “I’m running a multi-step task”

Wizards, imports, onboarding — any linear sequence the user starts and completes in one sitting. These deserve their own archetype (Focus View) rather than being crammed into a Dialog.

┌─ ≤ 1 field per step, ≤ 2 steps ─→ DIALOG
│ (trivial flow, no distraction risk) (short form, keep in context)
Multi-step task ───────────┤
├─ 3+ steps, sequential, must finish to ─→ FOCUS VIEW (3d)
│ commit (import, onboarding, complex (full-screen takeover, no
│ creation, destructive migration) sidebar, linear stepper)
└─ Steps are revisitable / optional / ─→ FORM archetype with
user dips in and out tabs or collapsible sections
(not a wizard — a settings form)

Focus View rules (from attio-layout-patterns.md §3d):

  • Main-pane takeover, sidebar visible — the focus surface owns the main content area; the global nav stays addressable. Mount the route inside AppLayout.
  • top-left — closes/aborts the flow, returns to parent page. Not “back.”
  • Two-row header — row 1 close + breadcrumb (h-12); row 2 stepper (h-12). Stepper gets its own row to breathe.
  • Linear stepper — numbered steps connected by chevrons (① ② ③). Future steps muted. No random-access.
  • No body tintbg-background, same as the rest of the app. Focus is established by structure (header + sidebar context + action bar), not surface color.
  • Centered, max-width content — never edge-to-edge.
  • Sticky bottom action bar — Cancel / Previous / Next, full-width container with right-aligned cluster. Pass via the actions prop on <FocusLayout>. Floating-pill action clusters are forbidden.
  • No app-level actions at top — the flow owns the user’s attention until abort or complete.

“Wizard-in-a-Dialog” smell (same family as “Dialog that outgrew itself”):

Promote to Focus View when your <Dialog> has any of:

  • A tab bar, stepper, or “Step 1 of 4” label inside the body.
  • Content that changes shape across steps (drop zone → table → summary card).
  • Back / Next buttons inside the DialogFooter.
  • showCloseButton={false} with a custom pinned to the corner.
  • max-w-4xl+ or h-[85vh]+.

The current /agents/:id/knowledge-base upload Dialog (KnowledgeBaseTab.tsx:675–771) has the first three — it’s a wizard crammed into a Dialog. Candidate for Focus View promotion once upload grows to include post-index verification steps.


Decision tree 2.5 — Dialog vs Drawer vs Page

Most common mistake after 3a-Split-overreach: picking the wrong overlay.

WHAT ARE YOU SHOWING / ASKING?
├─ Yes/no or a single-step commit (≤4 fields, no rich widgets)
│ ├─ Confirmation → <ConfirmPopover> (routine) / <ConfirmDialog> (catastrophic)
│ └─ Short form → <Dialog>
├─ Peek, read-only preview → Hover card popover (§6 in patterns doc)
├─ Config side-task while staying in list → <Sheet> / drawer
├─ Triage a collection, peek many records fast → <Sheet> / drawer (3c), URL-backed
└─ Deep read/edit, shareable, >2 min attention → Dedicated page

What Attio actually does (captured from live app + help docs):

SurfaceAttioWhy
Click a record row (contact / deal / company)Full page (3a Split)Records have rich activity; page is the right size
Click a kanban cardFull page (3a Split)Page-first for records
Hover entity namePopover hover cardPeek only, never commits to layout shift
Delete confirmationDialogYes/no
”Create new list” / “Invite teammate”Dialog (1–3 fields)Focused one-shot input
Edit view config / filter builderSheetUser keeps list context

Attio is page-first for records, dialog-first for short commits, sheet-second for config side-tasks. It is not drawer-heavy like Linear or Notion.

When to reach for each

NeedUseWidth / height caps
Yes/no confirm, reversible<ConfirmPopover>Anchored to trigger
Yes/no confirm, catastrophic<ConfirmDialog>Default Dialog sizing
Short form, 1–4 fields<Dialog>sm:max-w-md to sm:max-w-lg only
Read-only quick peekHover card (Popover)300–400px, auto-height
Config side-task (edit view, filter, attribute)<Sheet> from right400px for narrow, 600px for wide
Record preview during triage<Sheet> from right, ?id=<x> in URL500–600px, full viewport height
Record read/edit, shareable, long attentionDedicated page (3a Split)<PageContent> + <DetailPageBar>
Multi-step wizard (import, onboarding, complex create)Focus View (3d)Full viewport, sidebar hidden, max-width-constrained content

Rules

  • Never stack overlays. No drawer-inside-dialog, no dialog-over-drawer.
  • URL-backed drawer (?doc=<id>) beats unparameterized — Esc closes, browser back closes, link is shareable.
  • Drawer overlays, never pushes. Parent stays the same width.
  • Page is the canonical form for any record deep-linked or worth >2 min of attention. Drawer is a shortcut for triage, not the destination.

The “Dialog that outgrew itself” smell

You’re in the wrong primitive when your <Dialog> acquires any two of these:

  • Width escalation: sm:max-w-2xlmax-w-4xlmax-w-6xl
  • Height hacks: h-[85vh], max-h-[90vh], nested overflow-auto
  • Custom header replacing <DialogHeader> (often with showCloseButton={false})
  • Tabs or split layout inside the body

Fix: promote to Sheet (triage context) or Page (shareable / long-attention).

Concrete example in nx-agent today: the KB doc preview at KnowledgeBaseTab.tsx:780–1124 hits all four. It’s a Dialog pretending to be a page.


Decision tree 3 — “I’m organizing subsections”

┌─ N ≤ 8, mutually exclusive, ─→ TABS
│ user looks at one at a time (DetailPageBar tabs)
├─ User compares / edits across ─→ SPLIT PANE
Organizing subsections ────┤ two simultaneously (SplitContent)
├─ Reading top-to-bottom, each ─→ COLLAPSIBLE SECTIONS
│ section optional (ContentSection collapsible)
└─ Two/three equivalent views ─→ SUB-TOOLBAR
of the same data (intra-tab switcher)

Rules.

  • Tabs break at 9. Our current agent detail has 9 — it’s already broken. Split the page into two detail URLs.
  • Split pane (<SplitContent>) is for comparison or cross-editing, not “I have a sidebar of links”. Navigation sidebars belong at the shell level, not inside content.
  • Collapsible sections are the Attio default for long-scroll pages (Record Details right panel, Settings, Dashboard). Use <CollapsibleSection> (Plan 2 primitive) — state persists per-user in localStorage.
  • Sub-toolbar is an acceptable fourth tier inside a tab (spec exception). Keep it light — text segments, not a second tab row.

Decision tree 4 — Card vs flat

From attio-design-language.md L152–162, extended with nx-agent worked examples.

Use <Card> when…Use flat <FormSection> / <PropertyList> / <Separator> when…
Dashboard metric tile (label + big number + trend)Form field groups inside a settings page
Clickable entity preview (agent card, tool card, knowledge-base card in a grid)Right-side property panel (<RightPanel> content)
Empty-state CTA (large centered “create your first agent” block)Sequential form sections (name → description → permissions)
Danger zone (red-tinted card, always last)Split-form property panel on the right
Widget on a workbench (chat panel card, recent-runs card)Any “settings” sub-tab

nx-agent worked examples.

CaseCard or flat?Why
Agents list — each agent as a tile on the home pageCard (<CardGrid columns={3}>)Clickable entity preview
Agent settings form (name, description, system prompt)Flat (<FormSection> + <Separator>)Form field groups
Agent → Channels tab (one “card” per channel)Card (<CardGrid>)Clickable entity previews
Agent → Variables tab (key-value pairs)Flat (<PropertyList>)Key-value properties
Agent → Tools tab (sidebar list + right config)Flat split (<SplitContent> + <PropertyList>)Editing properties
Agent → Widget tab (form + preview iframe)Flat (main) + Card (iframe preview)Form is flat, the preview block is a card
Dashboard — usage metrics rowCard (<StatRow>, underlying metric cards)KPI tiles
Dashboard — recent activity tableFlat (edge-to-edge <DataTable>)Attio rule: tables don’t wrap
Review inbox (conversation list + preview)Flat (<InboxList>)Inbox archetype
Delete agent confirmation<ConfirmDialog> (not a card)Catastrophic confirm — dialog, not settings card
Danger zone on agent settingsCard (red-tinted)The single “use card” exception in settings

Per-screen mapping — nx-agent routes

Every route in the admin and chat apps, mapped to its archetype + Attio-aligned layout. Current column is what exists today; Target is what we should migrate to. Notes call out blockers or spec violations.

Admin app

RouteArchetypeCurrent layoutTarget layout (Attio-aligned)Notes / blockers
/agentsListListPageBar + <DataTable> (agents, status, actions)Same — flat <DataTable> only, no view switcher. Agents is a registry, not a task/flow. Presentation is known.DataTable default switches to variant="flat" (no border wrapper).
/agents/:idDetail (shell)DetailPageBar with 9 tabs + <Outlet/>Split into two detail pages — e.g. /agents/:id (core: Agent, Workflow, Knowledge Base, Tools, Variables) + /agents/:id/ops (Tests, Versions, Channels, Widget). Max 8 tabs per page.Spec violation today (9 tabs). Reducing is a separate migration plan.
/agents/:id (Agent tab)Form (Attio Settings)Single-column form<PageContent narrow> + flat <FormSection> groups + <Separator> between them + <ActionBar> with Save/Cancel. Follows Attio’s Settings archetype (full-width single column, no right panel, no card wrappers). System prompt is a large <Textarea> inside its own FormSection.Not a 3a Split — there’s no activity/timeline to put in the main area; properties are the content. Low-lift: current layout mostly works, just enforce flat FormSection + Separator and remove any card wrappers. Inline-edit (<PropertyList>) is optional and only if every field is independently savable.
/agents/:id/workflowCanvas (3b full-page)Full viewport xyflow canvas + left panel + command paletteKeep. This is the archetype’s poster child.Already Attio-aligned.
/agents/:id/knowledge-baseList (tab) + Drawer (preview)Table in the tab + Dialog at 90vh/6xl for preview containing nested split (left info panel + right content viewer, both with own overflow)Tab: flat edge-to-edge <DataTable>. Doc preview: drawer (3c) opened from row click, URL-backed ?doc=<id>. Drawer body = flat <PropertyList> (metadata) + <CollapsibleSection> (RAG index status, replacing the inline muted-bg mini-card) + document viewer as main content + “Open full page” action. Transient progress/result banners become flat alerts, not cards. Filter chips replace the outline-button row.P0 — multiple violations today: preview-in-Dialog (should be drawer or page), nested scrolls inside dialog, table border-wrapper, cards used as status banners, buttons-as-filters instead of chips, link-as-button for filename. Optional Plan N+1: /agents/:id/knowledge-base/:docId dedicated page for long-form doc reading, linked from the drawer’s “Open full page” action.
/agents/:id/toolsForm (Attio Settings) with list navSplit with select panel + config panel; nested scrolls<SplitContent> left=tool list (selection only), right=flat settings for the selected tool: <FormSection> groups + <Separator> OR <PropertyList> with inline-edit if every field is independently savable. No <RightPanel> — the right side is the main content, not a property sidecar.Nested-scroll violation today. Not a 3a Split. The “split” is list-for-nav + settings-for-content, not main-and-properties.
/agents/:id/testsDetail with sub-tabsSub-tabs (Test Suite List / Playground / History / Results), each with split layoutsPromote Playground to a full workbench page (3b). Suite list becomes a list with <HoverCardPreview> on suite name. History/Results earn a view switcher: default Table (latest runs), optional Kanban grouped by pass/fail/pending for active triage.Multiple screens to split — own plan. Second screen that gets a view switcher.
/agents/:id/widgetForm (Attio Settings)Form + iframe preview + API key table<PageContent narrow> + flat <FormSection> groups for widget config. Preview iframe is a single <Card> (self-contained widget, one of the valid card cases). API key table goes edge-to-edge flat below. No right panel — the config is the content.Not a 3a Split. Follows Attio Settings: flat single-column, card only for the preview (genuine self-contained widget).
/agents/:id/versionsList (inside detail)Table of versionsKeep as table, edge-to-edge flat. No view switcher (audit log, not a flow). Row hover-card shows diff summary.Low-lift.
/agents/:id/channelsList (inside detail)Channel cards + formsKeep as <CardGrid> (channels are clickable entities). Add channel → opens drawer (3c) with channel settings.Low-lift.
/reviewInboxConversation list + preview (custom)<InboxList> primitive (Plan 2). j/k keyboard navigation, e to archive, unread dot marker. View switcher earns its keep here — reviewers triage an inbox (default) but also want a kanban by review-status (pending / approved / rejected) when clearing a backlog.New archetype. Deprecates the custom split. One of only 2–3 screens that get a view switcher.
/review/:idDetail (3a or 3c)Call recording player + markupDrawer (3c) when opened from /review; full page (3a split with <RightPanel> for metadata) when deep-linked.Reuses drawer primitive from Plan 2.
/toolsListTool registry with raw HTTP viewerTable, edge-to-edge. No view switcher (registry, not a flow). Raw HTTP viewer opens in a drawer (3c), not inline.Removes the current inline <pre> with overflow-auto.
/settings/billingFormForm with payment fields<PageContent narrow> + <FormSection> + <Separator> — flat settings layout. Danger zone = red-tinted card at the bottom.Low-lift.
/settings/securityList (security audit)Audit log tableEdge-to-edge <DataTable>. No view switcher (audit log). Row hover-card preview shows audit detail.Low-lift.

Chat app

RouteArchetypeCurrent layoutTarget layoutNotes
/botsList (entity grid)List of available agents (cards or list)<CardGrid columns={3}> — agents are clickable entity previews.Cards stay here — it’s an entity grid, not a dense collection.
/chat/[botId]/[threadId]WorkbenchHeader + message list + input barKeep. Chat is the canonical workbench.No migration needed.
/embed/[botId]Exempt (iframe embed)Minimal widget UIExempt from shell per spec.No migration needed.
/user/loginExempt (auth)Centered auth formExempt from shell per spec.No migration needed.

Focus View (3d) candidates in nx-agent

Not tied to a specific route — these are flows that currently don’t exist or are crammed into Dialogs:

FlowTodayTarget
KB bulk upload + index verificationDialog at KnowledgeBaseTab.tsx:675–771 — drop zone → pending list → upload. Already a 3-step wizard in Dialog shape.Focus View (3d) if we add post-upload verification (review indexing status → confirm push to vector DB). Today’s 2-step flow can stay in Dialog; growing to 3+ means promote.
Agent creation wizardDoes not exist — creation is a quick-create dropdown or a simple form.Focus View (3d) if we ever add a multi-step onboarding (choose template → configure model → connect tools → review → create). Until then, a dedicated /agents/new Form page is sufficient.
Agent cloning / templatingDoes not exist.Focus View (3d) if cloning grows beyond “pick source, click clone.” Single-step clone = Dialog.
Workspace onboardingDoes not exist.Focus View (3d) when added — first-run experience, guided setup of first agent.
CSV / JSONL import for variables or knowledgeNot implemented.Focus View (3d) when added — mirrors Attio’s /deals/import/new shape.

Migration priority (blockers first)

  1. P0 — spec violations. /agents/:id/knowledge-base, /agents/:id/tools nested-scroll fixes. Need <RightPanel> + <PropertyList> from Plan 2.
  2. P0 — 9-tab agent detail. Split into two detail pages.
  3. P1 — table wrappers. Flip <DataTable> default to variant="flat" and audit all callers (~14 pages).
  4. P1 — /review inbox. Replace custom split with <InboxList>.
  5. P2 — view switcher (narrow). Only on /review (inbox default, kanban for triage backlog) and /agents/:id/tests history/results (table default, kanban by pass/fail for active triage). Do not add to /agents, /tools, /versions, /security — those are registries / audit logs with one obvious presentation.
  6. P3 — hover-card previews. Add across admin tables.
  7. P3 — inline-edit everywhere. Add <PropertyList> to remaining detail tabs once it’s battle-tested.

Red flags — signs you picked the wrong pattern

If you catch yourself writing any of these, go back and re-pick the archetype.

Red flagReal problemFix
Two overflow-y-auto on the same pageWrong archetype (you picked Split but the main area also wants to scroll its own sub-region)Move the sub-scroll contents into <RightPanel> (which is the only allowed second scroll zone) OR switch to Tabbed full-page (3b)
9+ tabs on a detail pageOne page doing two jobsSplit into two detail URLs
Table wrapped in <Card> or <div className="rounded-lg border">Non-Attio table chromeEdge-to-edge. Remove wrapper. (Spec rewrite enforces this; Plan 2’s DataTable default = flat.)
Modal form with > 4 fieldsModal should’ve been a drawer or a form page<Sheet> or <PageContent narrow> + <ActionBar>
<Dialog> at 80–90vh containing a nested split / long contentDialog is being used as a pseudo-pageDrawer (3c) for triage context, or dedicated detail page for long-form reading. Dialog is for confirmations + ≤4-field quick edits only.
<Card> wrapping a progress bar, alert, or toast-like status bannerBanner is not a “content container”Flat alert: role="status" or role="alert" div with icon + text, separated by spacing or <Separator>, not a card boundary
<Dialog> containing a stepper, tab bar, or “Step 1 of 4” inside the bodyWizard crammed into a DialogFocus View (3d) — full-screen takeover with close, linear stepper, Next/Previous bottom-right. Dialog is for single-step commits.
Agent / resource creation as a multi-field modal form”Create” is not a confirmation, it’s a flowFocus View for ≥3 steps or ≥6 fields; otherwise dedicated /new Form page. Dialog only for trivial “name it” creations.
Breadcrumb trailWrong navigation modelBack button in <DetailPageBar> — always goes to parent list
Primary button inside a section headerWrong zoneSection actions are outline sm; primaries live in the page bar or action bar
<Card> around a right-side property panelNon-Attio patternFlat <PropertyList> with <CollapsibleSection>
toast.success() after a click-triggered mutationWrong feedback surface<ActionButton successText="…">
Full-page loading spinnerWrong loading state<Skeleton> inside <PageContent>
position: sticky inside contentPage bar is already fixedNothing else needs to stick
Gallery / timeline / map view for a record listOver-reach beyond AttioUse Table. If you genuinely need calendar, that’s fine — but stop before inventing a new view type.
Two primary buttons in the same neutral decisionBoth should be outlineOne primary max per zone
<h1> inside contentPage-bar title covers thisUse <ListPageBar title> / <DetailPageBar title>

Quick-reference decision card

Print this. Tape it next to your monitor.

ARCHETYPE → List | Detail | Form | Workbench | Dashboard | Canvas | Focus
COLLECTION → Table (default) | Kanban (pipeline) | Calendar (time) | List (<20, visual)
DETAIL → Split (3a, properties first) | Tabbed (3b, content first) | Drawer (3c, list first) | Focus (3d, linear task)
SUBSECTION → Tabs (≤8) | Split (compare) | Collapsible (scroll) | Sub-toolbar (equivalent views)
CARD → KPI tile | Entity preview | Empty CTA | Danger zone. NOTHING ELSE.
FLAT → Form, properties, settings, panels, split-form right side. EVERYTHING ELSE.
SWITCHER → Only on task/flow screens (/review, /tests). Registries + audit logs = one view, no switch.
OVERLAY → Dialog (≤4 fields, yes/no) | Sheet (triage / config side-task) | Page (shareable, long attention) | Focus (3+ step wizard)
Attio = page-first for records, dialog-first for short commits, sheet-second for config, focus for wizards.

See also