Attio Layout Patterns - Deep Reference
Companion to attio-design-language.md. Where that file captures tokens and archetypes, this one captures patterns — concrete mechanics of how Attio composes screens, and when to reach for each.
Sources. Primary: live inspection of app.attio.com (April 2026), captured in attio-design-language.md. Secondary: Attio help center (attio.com/help/reference/*, verified April 2026) and the design-philosophy article at strategybreakdowns.com/p/how-attio-does-design. Where a pattern is inferred and not directly sourced, it’s marked [inferred].
How to read this doc. Each section describes a pattern, its anatomy, when Attio uses it, and the decision rules for our own screens. For per-screen nx-agent recommendations, see attio-layout-decision-guide.md.
1. Collection views
Attio’s first-principles idea: one underlying dataset, many representations. The user picks the representation from a view-switcher at the top of the list page. The switch is instant — same URL, same filter state, different shape.
Officially supported view types (Attio help center, April 2026):
| View | Use when | Row / card anatomy | Attio source |
|---|---|---|---|
| Table | You need row-level scan across many attributes. Spreadsheet-familiar. | Row per entry; column per attribute. No card wrapping. 12px muted column headers. Footer row with count + per-column calculations. | ”Table views allow you to manage your list records and attributes in a familiar spreadsheet style.” |
| Kanban | Entries move through a pipeline. Status is the primary axis. | Column per stage (a status-type attribute). Card shows avatar + title + 2–3 key fields. Drag-drop between columns, multi-select with shift-click or per-card checkbox. | ”Kanban views enable you to visualize records as cards progressing through stages of a pipeline or process.” |
| Calendar [inferred] | Time is the primary axis (due dates, scheduled meetings). | Month / week grid; entries render on their date. | Mentioned in passing in the kanban docs (“switching a list from table to kanban or calendar feels instantaneous”); no dedicated help page found as of April 2026. |
What Attio does not have as a view type (despite common CRM conventions): Gallery, Timeline, Map, Grid. If we need one of those, we either (a) build it as a tab inside the Detail archetype (not a collection view), or (b) add it as an nx-agent extension and call it out as a local deviation from Attio.
Table view mechanics
- Edge-to-edge. No
border-radius, no<Card>wrapper around the table, no shadow. The table spans the full content width. - Column header. 12px, muted foreground,
font-medium— not uppercase, not bold. - Row hover. Subtle muted-bg fill. Click to open record (full page or drawer — see §3).
- Bulk select. Hovering a row reveals a checkbox in the left gutter. Shift-click range-selects. Selected state shows a toolbar at the footer with bulk actions.
- Footer row. Always present. Left side: total count. Each column has a ”+ Add calculation” slot — optional per-column aggregate (sum, avg, min, max, count distinct). Sticky to the bottom of the scroll zone.
- Density. Single default density. Attio does not expose “compact / comfortable” options; the single density is tight by most CRMs’ standards (~36px row height).
- Saved-view state. Column visibility, order, width, sort, filter, and group-by are part of the saved view. Each list has one or more named views (e.g., “All contacts”, “My contacts this week”); users switch via a dropdown / segmented control above the table.
Kanban view mechanics
- Column = a status value. The kanban is always grouped by a single status-type attribute. Attio’s docs call this “group your kanban view around any of the status type attributes in your list.”
- Column header. Stage name + count. Click the header to get the “Hide stage / Delete stage” menu.
- Card anatomy. Avatar (or logo), title row (record name), 2–3 attribute rows. No shadow at rest.
- Drag-drop. Single-card or multi-select drag. Multi-select: hover a card, the checkbox appears top-right; shift-click to range-select within a column.
- Empty / “No stage” column. Always shown by default; hide via
View settings > Visible columns > No status. - WIP limits. [inferred] Attio’s public docs do not surface WIP limit features.
- Card click. Opens the record page — typically as a drawer overlay (see §3) so the user keeps the board in context, not a full navigation.
Calendar view
Minimally documented publicly. Treat as: month or week grid, entries on their date. Do not invest heavily in calendar patterns unless a concrete nx-agent use case appears — Attio has not standardized it the way Table and Kanban are standardized.
The underlying rule
“Each representation uses the same underlying data, but adapts to its context.” — strategybreakdowns.com, on Attio’s design philosophy.
For nx-agent: never fork data-fetching per view. The table, kanban, and calendar of the same list share the same query, filter, and sort state. The view is purely a presentation layer.
2. View Switcher
The control that flips between collection views on a single list.
Attio’s implementation. A compact segmented control at the top-left of the content area, showing the current view icon + name. Clicking opens a menu of saved views; ”+ New view” creates a fresh one. Icon per view type (grid for Table, columns for Kanban, calendar for Calendar).
State persistence.
- View definition (columns, filter, sort, group-by) is workspace-scoped and saved per named view.
- Current view selection is URL-scoped — deep links preserve which view the recipient opens to.
- Column widths [inferred] are per-user local preference, not part of the shared saved view.
Decision rules for introducing a view switcher to an nx-agent screen. The switcher is a task/flow pattern, not a universal capability. Apply only when all four are true:
- Task/flow nature. Users triage, move items through stages, or work a queue — not “browse configuration.” Registries (agents, tools, versions) and audit logs (security events) fail this rule: we know how to present them, and nobody benefits from switching.
- Both views genuinely useful for the same user on the same data, not “one real, one vanity.”
- Same query, filter, sort state. If the kanban pulls different data than the table, they’re two pages, not two views.
- Users switch back and forth within a session, not pick once and forget.
Attio’s model works because CRM data genuinely is task/flow-heavy: deals pass through stages, contacts get worked through, sequences progress. nx-agent has far fewer surfaces with that shape.
Earned in nx-agent: /review (inbox vs kanban by review-status), /agents/:id/tests history/results (table vs kanban by pass/fail), possibly workflow run log.
Not earned: /agents, /tools, /agents/:id/versions, /settings/security, any settings sub-tab. Pick the single right view and ship it.
We ship a <ViewSwitcher> primitive for the few surfaces that qualify. It takes a list of view descriptors ({ id, label, icon, render }) and emits the current view via URL param ?view=<id>. Default is always the “scan” view (usually Table or List), not the “triage” view.
3. Detail layouts
Attio uses three shapes for “show me one record.” Picking the wrong one is the single most common layout mistake.
3a. Split — main + right panel
The default for any CRM-style record (Company, Person, Deal). The canonical Attio detail.
┌─ TopBar: ← 1 of N │ Record Name ☆ │ Actions ─────────────────────────┐│ Tabs: Overview │ Activity │ Emails │ Calls │ Team │ Notes │├─ Main (scrollable) ─────────────────────┬─ Right panel (scrollable) ──┤│ │ Tabs: Details │ Comments ││ § Highlights │ ││ [metric cards] │ ▾ Record Details ││ │ ⊙ Domain google.com ││ § Activity │ ⊙ Name Google ││ timeline entries │ ⊙ Team No team ││ │ > Show all values ││ § Notes │ ││ note entries │ ▾ Lists ││ │ This record has not... │└───────────────────────────────────────────┴──────────────────────────────┘Key rules (copied from attio-design-language.md and confirmed against Attio help docs on record pages):
- Right panel is not a
<Card>. It’s a flat property list with collapsible sections, separated by subtle horizontal dividers. - Right panel has its own tab bar. Details / Comments by default. Clicking a tab swaps only the right-panel contents — the left stays put.
- Each section on the right is collapsible. ▾ chevron on the header toggles; state persists per section.
- “Record Details” is mandatory. Attio help docs: “Each record page must include a Record Details section, and the Lists section can’t be removed.” Users can rename and reorder other sections.
- Property rows are inline-edit. Click the value to edit in place — no modal, no separate edit mode. Empty values show a “Set a value…” placeholder.
- Highlights section on the left uses metric cards. This is the only place on a record page where cards appear. Everywhere else is flat.
When to use. The main area has genuine activity content — a timeline of emails, calls, notes, messages — that scrolls while the right panel holds static properties. Both halves must carry their own weight.
When NOT to use (common trap). If the only thing you can think of to put in the main area is “more of the properties you already have on the right,” you don’t have a 3a Split — you have a settings page. Use Attio’s Settings archetype instead (full-width single column, flat <FormSection>, no right panel). Configuration screens (name + description + model + prompt), integration settings, billing forms, widget embed config — all settings, none 3a.
nx-agent fit. /review/:id is a genuine 3a (conversation timeline main + properties right). Most /agents/:id/* sub-tabs are settings, not 3a.
3b. Tabbed full-page (no right panel)
Used when properties are not first-class — the tab content is the point.
┌─ TopBar: ← │ Title │ Actions ────────────────────────────────────────┐│ Tabs: Tab A │ Tab B │ Tab C │├─ Full-width content ────────────────────────────────────────────────────┤│ ││ Active tab's content, spanning the full main-area width ││ │└────────────────────────────────────────────────────────────────────────┘Same DetailPageBar as the split, but no right panel. The main content uses <PageContent> and fills the whole main area.
When to use. Each tab is itself a workbench (editor, canvas, dashboard) that wants full width. Agent workflow editor → this is the right shape.
3c. Side-panel drawer
Used for “peek at a record” without leaving the list. Triggered by clicking a row in a list or a card in a kanban.
┌─ List / Kanban (main) ──────────┬─ Drawer (400–600px, overlay) ──────┐│ │ ✕ close Record Title ││ The list stays visible and │ Tabs: Details │ Activity ││ interactive behind the drawer. │ ││ │ [same right-panel property list] ││ │ [some activity/timeline below] │└───────────────────────────────────┴────────────────────────────────────┘Key rules.
- Overlays, does not push. The list underneath stays the same width. The drawer is a scrim-less overlay (shallow shadow on the left edge).
- Deep-link round-trip. The drawer writes
?record=<id>to the URL. Closing clears the param. Sharing the URL opens the drawer on first paint. - Esc closes. Clicking outside closes. Back button closes.
- Promote to full page. Drawer has an “Open full page” action that navigates to the 3a layout and clears the URL param.
When to use. The list is the user’s primary context (inbox, kanban, bulk triage). Opening the record shouldn’t steal the context.
Decision rule, distilled
If properties are first-class → 3a Split. If content per tab is first-class → 3b Tabbed full-page. If the list is first-class → 3c Drawer.
Never mix: do not put tabs inside a drawer without a very good reason (two layers of tabs = confusing); do not embed a drawer inside a full-page detail (already deep).
3d. Focus View (main-content takeover, sidebar preserved)
A focused page that takes over the main content area for multi-step flows that deserve the user’s undivided attention: import wizards, onboarding sequences, complex creation flows. The global nav (Sidebar) remains visible — the focus surface is scoped to the main view, not the viewport.
Attio example: /deals/import/new — CSV import wizard. Sidebar still rendered on the left; the import flow occupies the main pane only.
┌─ Sidebar ─┬─ Main view ────────────────────────────────────────────────┐│ │ ✕ │ Deals › Import │ ← row 1 (close + breadcrumb)│ ▢ Home │─────────────────────────────────────────────────────────────││ ▣ Deals │ ① Upload file › ② Map columns › ③ Review › ④ Preview │ ← row 2 (stepper)│ ▢ Lists │═════════════════════════════════════════════════════════════││ ▢ Apps │ ││ │ ┌─ Drop zone ────────────────────────────────────────────┐ ││ │ │ [illustration] │ ││ │ │ Drop your .CSV here │ ││ │ │ or [ Choose a .CSV file ] │ ││ │ └────────────────────────────────────────────────────────┘ ││ │ ││ │ Learn more ││ │ ┌─ help card ──┐ ┌─ help card ──┐ ││ │ └──────────────┘ └──────────────┘ ││ │─────────────────────────────────────────────────────────────││ │ [ Cancel ] [ Next →] │ ← sticky action bar└───────────┴─────────────────────────────────────────────────────────────┘Anatomy:
| Element | Details |
|---|---|
| Close button | ✕ icon, top-left of the focus header (not top-right). Navigates back to the parent entity, not browser-back. |
| Header — row 1 | h-12 border-b px-3 — close button + breadcrumb. Title-only row, no actions. Breadcrumb is flat text, not links. |
| Header — row 2 | h-12 border-b px-3 — stepper occupies the full row. Numbered horizontal progress (①②③④) connected by chevrons. Two rows give the stepper room to breathe at all widths. |
| Sidebar | Visible. The focus view is scoped to the main content area; the global nav stays where it always is. Users can still see where they are in the app. |
| Content background | bg-background — same as the rest of the app. The focus surface does not tint its body. Earlier drafts referenced rgb(249,250,251) as a tinted body color; that was a misread (the rgb is from old Attio’s sidebar, not the focus body). The focus is established by the two-row header + sidebar context + sticky action bar, not by surface color. |
| Content area | Centered, max-width constrained. Large vertical padding. Step-specific (file upload → column mapping → review → preview). |
| Action bar | Sticky at the bottom of the focus view, border-t bg-background px-3 py-2. Right-aligned button cluster: Cancel (ghost) / Previous (outline) / Next (primary). Full-width container — never a floating pill. Mirrors the Form archetype’s ActionBar. |
| Help section | Below the main content, above the action bar — contextual “Learn more” cards linking to documentation. |
Key patterns:
- Main-view takeover, not viewport takeover — Sidebar visible, app stays addressable. The user can navigate away (with discard confirm) at any time.
- X closes, doesn’t go back — closing abandons the flow and returns to the parent page (e.g.
/agents). Esc same. - Linear stepper — not tabs. Steps are sequential, not random-access (can’t jump to step 4 before completing step 1).
- Centered layout — content is max-width constrained within the focus view, never edge-to-edge.
- Tinted background — subtle gray bg sets the focus surface apart visually. It is not a modal (no
role="dialog", no focus trap). - Sticky bottom action bar — Next/Previous/Cancel anchored at the bottom edge of the focus view, full-width container with right-aligned buttons. Mirrors the Form archetype’s bottom-fixed action bar pattern, NOT a floating pill.
When to use:
- Multi-step wizards (import, export, onboarding, complex creation)
- Tasks that are “start-to-finish” — user should complete or abandon, not multitask
- Flows where sidebar nav is a distraction
- Flows that are too long for a Dialog and too sequential for a regular page
When NOT to use:
- Single-step forms (use Dialog or settings page)
- Configuration that the user dips in and out of (use Sheet or settings page)
- Record detail (use Split or Tabbed full-page)
Mapping to nx-agent:
- Earned: Agent creation wizard (if we add one), bulk import, onboarding flow
- Not earned: Agent settings (too interruptible), individual tool/KB config (too small)
Decision rule, updated
If properties are first-class → 3a Split. If content per tab is first-class → 3b Tabbed full-page. If the list is first-class → 3c Drawer. If the task is linear and undivided → 3d Focus View.
3e. Dialog vs Drawer vs Page — the honest decision
The most common mistake after 3a-Split-overreach is picking the wrong overlay. Dialog, Drawer (Sheet), and Page are all “show me more of one thing,” but they have very different contracts.
What Attio actually does
| Surface in Attio | Choice | Why |
|---|---|---|
| Click a row in a records table (contacts, deals, companies) | Full page (3a Split) | Records have rich activity — a page is the right size |
| Click a kanban card | Full page (3a Split) | Same — page-first for records |
| Hover the entity name in a row | Popover (hover card, §6) | Peek only; never commits to a layout shift |
| ”Delete this?” | Dialog (ConfirmDialog) | Yes/no confirmation |
| ”Create new list” / “Invite teammate” / “Move to list…” | Dialog (short form, 1–3 fields) | Focused one-shot input |
| Edit a single view’s config (filter, sort, columns) | Sheet (side-drawer) | The list context matters — user sees other views / data while editing |
| Edit a single attribute’s definition | Sheet | Config side-task |
| Build a complex filter | Popover or Sheet | User stays on the list while building |
The real rule Attio embodies: page-first for records, dialog-first for short commits, sheet-second for configuration side-tasks. It’s not a drawer-heavy system like Linear or Notion.
The decision tree
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, no commit)│ ↓│ Hover card popover, 300–400px, key fields + quick actions│├─ Configuration / sub-setting where the list context matters│ ↓│ <Sheet> / drawer — triggered from within a parent flow, dismissible, URL-backed│├─ Browse/triage a collection — peek many records fast, acting on some│ ↓│ <Sheet> / drawer (3c) with URL `?record=<id>`│ (pragmatic pattern; Attio itself goes full page here, Linear/Notion use drawer)│└─ Read/edit a record deeply, share a link, or >2–3 minutes of attention ↓ Dedicated page (3a Split for records, Settings archetype for config)Concrete caps
- Dialog is for ≤4 fields, no embedded tabs, no scrolling body. If you’re reaching
h-[80vh]ormax-w-4xl, you’ve outgrown Dialog — promote to Sheet or Page. - Sheet/drawer widths: 400px (narrow — single-task config), 600px (wide — record preview with viewer). Beyond 800px it’s a page in disguise.
- Page is unbounded — use
<PageContent>and letmax-w-*do content constraint where needed.
Compositional rules
- Never stack overlays. No drawer-inside-dialog, no dialog-over-drawer. Radix technically allows it; UX suffers (two focus traps, two Esc behaviors).
- URL-backed drawer > unparameterized drawer.
?doc=<id>or?record=<id>makes the drawer shareable and makes browser-back work the way users expect. - Sheet/drawer does not push content — it overlays with a scrim-less shallow shadow on the leading edge. The parent stays the same width.
- Page is the canonical form for any record that will be deep-linked, shared, or read for more than a couple of minutes. Drawer is a shortcut for triage ergonomics, not the destination.
Common migration: “a Dialog that became too big”
Symptoms:
sm:max-w-2xl→max-w-4xl→max-w-6xl.DialogContentheight hacks:h-[85vh],max-h-[90vh], nestedoverflow-auto.- Custom dialog header with
showCloseButton={false}replacing<DialogHeader>. - Tab bar or split layout inside the dialog body.
When you see two or more of these, the Dialog has outgrown itself. Move to:
- Sheet (drawer) if the parent list is the context the user wants to return to quickly.
- Page if the content deserves a shareable URL and >2 minutes of attention.
4. Right-panel Record Details — anatomy
The most distinctive Attio component we don’t currently have in @na/ui. Breaking it down so the primitive can be built exactly.
Structure
<RightPanel> <PanelTabs> <PanelTab>Details</PanelTab> <PanelTab>Comments</PanelTab> </PanelTabs> <PanelContent> <CollapsibleSection title="Record Details" defaultOpen> <PropertyList> <PropertyRow icon={Globe} label="Domain"> <InlineEditField value="google.com" type="url" /> </PropertyRow> <PropertyRow icon={Type} label="Name"> <InlineEditField value="Google" type="text" /> </PropertyRow> {/* ... */} <ShowAllValuesToggle /> </PropertyList> </CollapsibleSection> <CollapsibleSection title="Lists" defaultOpen> {/* list memberships */} </CollapsibleSection> </PanelContent></RightPanel>PropertyRow anatomy
Every row is three parts, left-to-right:
- Icon (14–16px, muted). Type-specific: globe for URL, type-icon for text, calendar for date, user for person, etc.
- Label (12px
font-medium, muted, fixed-width left column — typically 120–140px wide for alignment). - Value — the interactive surface. Default state is read-only text; click transforms the cell in place to an editable
<Input>/<Select>/<DatePicker>/<Combobox>depending on the property type. Blur or Enter commits; Esc cancels.
Inline-edit rules
- No save button. Blur commits silently; success shows a brief
<MutationStatus>inline (matches nx-agent<ActionButton>conventions). - Optimistic update for low-latency feedback. Rollback on failure with inline error next to the row.
- Empty value shows “Set a value…” in muted foreground, italic. Click to edit.
- Validation inline below the value, red text, one line.
- Keyboard.
Tabfrom label to value.Entercommits.Esccancels.↑/↓move between rows when focused on a value.
CollapsibleSection rules
- Chevron on the left of the section title (▾ expanded, ▸ collapsed).
- Section title is 14px
font-medium, mirrors the<ContentSection>style. - State persists in
localStoragekeyed by(routeId, sectionId). Not URL-backed — collapse is personal, not shared via deep-link. - “Show all values” toggle at the bottom of a
PropertyListwhen Attio is hiding low-priority attributes. Click to reveal the rest.
5. Inbox pattern
Attio uses this for its notification center and for any “queue of items the user is working through.” It’s not listed as an archetype in Attio’s public docs, but it’s consistently shaped across the product.
Anatomy
┌─ Topbar: Inbox title + unread count + [archive all] ─────────────────┐├─ List (left, 360–420px, scrollable) ─┬─ Detail / preview (right) ────┤│ ● Item A — timestamp │ ││ summary line │ Full detail of selected item ││ ● Item B │ ││ summary line │ ││ Item C (read — dimmed) │ │└──────────────────────────────────────┴────────────────────────────────┘Keyboard contract (inferred from Attio; standardize across nx-agent)
j/↓— next itemk/↑— previous iteme— archive current itemEsc— focus back to list, or close if in detailEnter— open the item in its canonical detail page (leave inbox)⌘Enter— mark read and move to next
Rules
- Unread state is a filled circle on the left of the row; read state removes the circle (not just dims color).
- Preview is flat. No card wrapping. Same property-list primitive as
<RightPanel>where applicable. - Empty state is a friendly illustration + “You’re all caught up.” — no “Inbox zero” gamification.
- The list never re-orders out from under the user. New items appear at the top with a muted “new since last fetch” divider, not inserted mid-list.
nx-agent fit. /review (conversation QA queue) is an inbox, not a list. Current implementation is a list + custom preview — migrate to <InboxList>.
6. Hover-card row preview
When the user hovers the entity name in a list row, a popover appears with a 200ms delay showing key information + quick actions. The user can act without clicking into the record.
Anatomy
- Trigger. The entity name (link) in a table/list row.
- Delay. ~200ms on open, ~100ms on close. Short enough to feel responsive, long enough to avoid accidental opens while scanning.
- Content.
- Avatar / logo (32–40px).
- Name (same as trigger, larger).
- 3–5 key property rows (same
PropertyRowprimitive). - Recent activity (1–2 lines, optional).
- Action footer:
Copy link,Open in new tab,Open full page. Optional contextual actions (“Archive”, “Snooze”).
Rules
- Anchor to the trigger, not the cursor. The popover should be stable — cursor movement within the row should not make it jump.
- Pointer-friendly. A 300–400px popover positioned below-right of the trigger, arrow pointing to the trigger.
- Don’t open on touch devices. Hover is desktop-only. On touch, tap goes directly to the record.
- Don’t put anything destructive in the action footer. Delete / archive / reset belong in the record page, not in a hover-triggered popover.
7. Slash menu + @ menu — universal rich-input
From strategybreakdowns: “The same ’/’ menu for headings, images, and lists. The same ’@’ menu for tagging teammates.” Attio’s text inputs — notes, comments, tasks, email composer, descriptions — all share this behavior.
Rules
/opens the block-type menu (heading, bullet, numbered, quote, divider, code block, image, checkbox).@opens the mention picker (teammates, records, lists).- Both open inline at the cursor, not as a separate dialog.
- The same component, everywhere. Improving the slash menu in one place improves it in all places — this is the lever strategybreakdowns identifies.
When to add to nx-agent
Only to surfaces that already want rich input: agent description, knowledge-base documents, review comments, test case notes. Not to property values (a single-line text property is a text input, not a slash-menu surface).
We don’t build this in Plan 2 (initial primitives pass) — it’s a later capability. Tracked in the plan’s “out of plan” list for a dedicated richtext plan.
8. Command palette (⌘K)
The global keyboard-first surface. Attio’s is consistent with Linear / Raycast / Notion.
Rules
- Single global trigger:
⌘K(macOS) /Ctrl+K(others). Also reachable from a⌘Kchip in the topbar. - Default scope = workspace. Searches records, lists, views, and exposes global commands (“Create record”, “Open settings”, “Go to Contacts”).
- Contextual scope on a record: palette pre-filters to commands relevant to the record (“Change owner”, “Add to list”, “Archive”).
- Fuzzy match by command label and record name.
- Empty state shows recent items + suggested commands.
nx-agent fit
Workflow editor already has a command palette (per Phase-1 exploration). Extend to the admin shell so any page can be reached from ⌘K. Not in Plan 2 — tracked separately.
9. Toolbar pill filters
Attio’s toolbar sits above the view, not as a floating button bar.
Structure
[ View switcher ] [ + Filter ] [chip: Stage = Open ✕] [chip: Owner = Me ✕] [ Sort ] [ ⌘ Actions ]Rules
- Chip per active filter. Pill-shaped, label + value +
✕to remove. - No dropdown-first filters. Clicking ”+ Filter” opens a picker → then the user picks attribute → then value. Result: a chip.
- Filters are combined with AND. No UI for OR; for OR semantics, save a different named view.
- Clicking a chip re-opens the picker in “edit” mode — change value without removing the chip.
- Sort is a separate chip/button to the right of filters — never interleaved.
- Bulk actions appear on the right of the toolbar and are only visible when rows are selected.
What we replace in nx-agent
Current <ContentToolbar> has a left slot (filters / search / sort) and a right slot (bulk / upload). We keep the component but add a <FilterChips> sub-primitive that renders the active chips in the left slot. Dropdown-style filters become the “pick an attribute” step inside the + Filter popover.
9b. Settings Table — bordered, non-paginated
Attio has two distinct table patterns that look similar but serve different roles. Confusing them is a common mistake.
The two table shapes
| Collection Table (§1) | Settings Table (§9b) | |
|---|---|---|
| Where | Main content area — Companies, Deals, People lists | Inside a settings page — Objects, Members, Exports |
| Border | None. Edge-to-edge, no wrapper | Rounded border wrapper (rounded-lg border) |
| Pagination | Yes — scroll-loaded or paginated for large datasets | None — all items rendered. Dataset is small/finite |
| Footer | Count + per-column calculations | No footer |
| Row actions | Hover checkbox (bulk select), row click → detail page | Per-row ⋮ menu (right edge), row click → detail or inline |
| Width | Full content width, grows with viewport | Content-constrained (inherits max-w from settings layout) |
| Background | Same as content bg (white) | Same as content bg, but the border creates visual containment |
| Header icons | Optional sort indicators | Small attribute-type icons before column names |
Settings Table anatomy (from Attio /settings/data/objects)
┌─ H1: Objects ──────────────────────────────── [+ New custom object] ──┐│ Modify and add objects in your workspace ││ ││ ┌─ Info banner (blue tint, optional) ───────────────── [Upgrade] ──┐ ││ │ ⓘ Make the most out of objects. Create custom objects... │ ││ └──────────────────────────────────────────────────────────────────┘ ││ ││ Q Search objects ≡ Filter ││ ││ ┌─ rounded-lg border ──────────────────────────────────────────────┐ ││ │ ⊙ Object 5/3 ⊞ Type 📊 Records ⚙ Attributes │ ││ ├──────────────────────────────────────────────────────────────────┤ ││ │ 🏢 Companies Standard 10 35 ⋮ │ ││ │ 📊 Deals Standard 1 11 ⋮ │ ││ │ 👤 People Standard No records 32 ⋮ │ ││ │ 👤 Users Standard No records 9 ⋮ │ ││ │ 🏢 Workspaces Standard No records 10 ⋮ │ ││ └──────────────────────────────────────────────────────────────────┘ ││ ││ (no pagination, no footer, no count) │└──────────────────────────────────────────────────────────────────────────┘Also confirmed in: /settings/workspace/members — same bordered table with User | Role | Teams columns, no pagination.
Key patterns
- Rounded border wrapper.
<div className="rounded-lg border">around the<Table>. This is the samerounded-lg borderwrapper our03-layout.mdalready documents for tables — it’s the standard table container in the design system. - No pagination. Settings tables are for finite, admin-managed collections (objects, members, API keys, webhooks). The full list fits on screen. If it doesn’t, the page scrolls — but never with row-based paging.
- Column header count badge. The primary column header (“Object”) shows a count badge (
5/3= 5 total / 3 standard). This is a compact way to show “N items” without a separate footer row. - Column header icons. Small icons before column names indicate the attribute type (grid icon for Type, chart for Records, gear for Attributes). This is informational — not sortable in the same way collection tables are.
- Per-row
⋮menu. Last column is a⋮(vertical dots) that opens a dropdown with contextual actions (Edit, Delete, Archive). Not a hover-revealed checkbox — single-item actions, not bulk. - Search + Filter toolbar sits above the table (outside the border wrapper), same
<ContentToolbar>pattern. - Info banner. Conditional upsell/info banner (blue tint) above the toolbar. This is an Attio-specific pattern for upgrade prompts.
How it differs from Attio’s main collection table
| Feature | Collection (Companies) | Settings (Objects) |
|---|---|---|
| Border wrapper | ✗ None — flat edge-to-edge | ✓ rounded-lg border |
| Row checkbox | ✓ Hover-reveal bulk select | ✗ No bulk select |
| Footer | ✓ Count + calculations | ✗ No footer |
| Pagination | ✓ Scroll-loaded | ✗ All rendered |
| Click action | Navigate to record detail | Navigate to object config (or inline edit) |
| Column header | Plain text, sort arrows | Icon + text, optional count badge |
Row ⋮ menu | Hidden until hover | Always visible (right-aligned) |
Decision rule
Is the collection finite and admin-managed? → Settings Table (bordered, no pagination,
⋮per row). Is the collection user-generated and potentially large? → Collection Table (edge-to-edge, pagination, bulk select).
Settings Table surfaces in nx-agent:
/settings/billing— invoices list/agentslist (currently 4-10 agents) — borderline; currently uses collection table style, acceptable/agents/:id/tools— finite list of configured tools/agents/:id/knowledge-base— finite list of uploaded documents/agents/:id/versions— version history (could grow, but still admin-scoped)/settings/security— API keys, webhooks
Collection Table surfaces in nx-agent:
- Conversations/chat history — user-generated, potentially thousands
- Review queue — large volume
- Any future CRM contacts/deals list
Mapping to existing 03-layout.md
Our existing table documentation in 03-layout.md already specifies the <div className="rounded-lg border"> wrapper — but it applies it to ALL tables. Attio’s distinction is subtler:
- Collection tables: the wrapper border is part of the content-level
<ContentSection>, not the table itself. Edge-to-edge within the section. - Settings tables: the wrapper border is directly on the table div. Contained within the settings page’s constrained width.
Both use the same <Table> primitive — the difference is the page archetype (List vs Settings/Form), not the component.
10. Flat sections — not cards
The recurring rule from attio-design-language.md:
Cards are for content containers — self-contained, potentially interactive entities. If the content is form fields, key-value properties, or configuration, use flat
<FormSection>+<Separator>.
Where Attio uses cards
- Dashboard metric / KPI tiles.
- Clickable entity previews (agent cards, tool cards in a grid).
- Empty-state CTAs (large centered card with icon + title + action).
- Danger zone — the only red-tinted card, always last in a settings page.
Where Attio does not use cards (and we mustn’t either)
- Settings / configuration sections — flat with
<Separator>between groups. - Form field groups inside a page —
<FormSection>has no card background. - Right-side property panels — flat
<PropertyList>. - Sidebar navigation items.
- Individual list rows (use table / list / kanban views, not card grids for dense data).
Section header without a card
Attio’s section headers across settings and dashboard:
Meetings (4)─────────── [separator][flat list of items]Not:
┌───────────────┐│ Meetings ││ (4) │├───────────────┤│ [list items] │└───────────────┘<ContentSection> already supports this — the migration gives it a count prop and a collapsible prop, no card background.
11. Spacing rhythm across patterns
Pulled from attio-design-language.md — repeated here because it’s the thing engineers get wrong most often:
- Base unit: 8px
- Label → input: 6px (
space-y-1.5) - Field group: 16px (
space-y-4) - Section spacing: 24px (
space-y-6) - Page padding: 24–32px (
p-6top-8) - Sidebar padding: 12–16px (
p-3top-4) - Card padding (where cards are used): 16px (
p-4)
Deviations force non-Attio visual density. The 10-review-checklist already catches off-grid spacing — these tokens are the grid.
12. Interaction timings
| Interaction | Attio timing | Source |
|---|---|---|
| Hover card open | ~200ms | inferred, measured via devtools |
| Hover card close | ~100ms | inferred |
| Tooltip open | ~400ms | Radix default — matches |
| Drawer slide-in | ~160ms ease-out | inferred |
| Inline-edit commit → fade to read-only | 0ms (instant) + 200ms status badge | inferred |
| Sidebar collapse | ~180ms ease-in-out | inferred |
| Optimistic update rollback | Up to 3s, then show error | inferred |
These belong in 02-foundations.md § motion once confirmed. For now, treat the Radix default (tooltip 400ms, popover 200ms) as baseline.
13. What’s not in Attio (and what that means for us)
Every design system is defined as much by absence as presence. Attio does not ship:
- Breadcrumbs (navigation is back-button + sidebar + tabs).
- Full-page loading spinners (skeletons only).
- Modal forms for >4 fields (uses drawer or full page).
- Two primary buttons in one decision (always one primary + outlines).
- Row hover effects on non-interactive rows.
- Shadows on resting cards.
- Grid view for records (cards in a grid) — only table / kanban / calendar.
- Timeline view for records (despite being a common CRM view).
- In-card borders / dividers / mini-tables nested inside cards.
If you find yourself reaching for one of the above, stop and check whether you’ve picked the wrong archetype. Nine times out of ten, the answer is: use the existing primitive (<Skeleton>, drawer, <PageContent narrow> form page, etc.).
Citations
attio-design-language.md(in-repo) — live inspection April 2026. Source of truth for tokens, archetypes, card/flat rule.- Attio Help Center —
attio.com/help/reference/managing-your-data/views/create-and-manage-kanban-views,.../create-and-manage-table-views,.../records/configure-record-pages,.../attio-101/attios-data-model/understanding-lists. Verified indexed April 2026. - “How Attio does design” —
strategybreakdowns.com/p/how-attio-does-design. Source for representation-vs-data and component-reuse philosophy. - Phase-1 codebase exploration of
agentic-ui/— source of the nx-agent screen inventory referenced in the decision guide.