13.1 Detail page 3-column shell
The host pattern for every record-detail page in the app — deal, contact, company, ticket. Left rail is a 260px summary card (who/what is this), centre is a flexible body with sticky tabbed header, right rail is 320px of contextual cards (AI suggestions, custom fields, related records). Right rail collapses to an icon strip on demand. Below 1100px the whole thing stacks.
Three-column detail
.detail-shellDefault state — left summary · centre body with tabs · right context. Click the chevron in the right-rail header to collapse it down to icons (gives the centre column more breathing room without losing context entirely).
BlueRock Health · Renewal Q2
<div class="detail-shell">
<aside class="ds-rail"> <!-- 260px summary -->
<div class="ds-summary-card">...</div>
<div class="ds-kv">...</div>
</aside>
<section class="ds-main"> <!-- centre, sticky header -->
<div class="ds-header">
<div class="ds-h-eyebrow">Deal</div>
<h1 class="ds-h-title">BlueRock <em>Health</em> · Renewal Q2</h1>
<div class="ds-h-meta">...metadata...</div>
<div class="ds-tabs" role="tablist">
<button class="ds-tab is-active" role="tab">Activity</button>
...
</div>
</div>
<div class="ds-body">...active tab content...</div>
</section>
<aside class="ds-rail ds-rail--right"> <!-- 320px context -->
<button class="ds-rail-toggle">...</button>
<div class="ds-context-card">...</div>
</aside>
</div>
<!-- Click .ds-rail-toggle to add .is-rail-collapsed; right rail
becomes a 56px icon strip without re-flowing the centre. -->.detail-shell { display: grid;
grid-template-columns: 260px minmax(0, 1fr) 320px;
gap: var(--s-5); }
.detail-shell .ds-rail { background: var(--bg-paper);
padding: var(--s-5); border-right: 1px solid var(--hair); }
.detail-shell .ds-header { position: sticky; top: 0;
background: var(--bg); z-index: 2; }
/* Right-rail collapse */
.detail-shell.is-rail-collapsed { grid-template-columns: 260px 1fr 56px; }
.detail-shell.is-rail-collapsed .ds-rail--right
> *:not(.ds-rail-toggle) { display: none; }
@media (max-width: 1100px) {
.detail-shell { grid-template-columns: 1fr; }
}13.2 Command palette
Open with ⌘K / Ctrl+K. Centred modal, paper surface, fuzzy filter on input. Result groups: Recent · Records · Actions · Reports · KB · Settings. Each row is icon + title + sub-label + shortcut hint. ↑/↓ navigates; Enter commits; Esc closes. Recent items are pinned when the input is empty.
Default state — empty input
.cmdkEmpty input shows recent items + top actions. The first row gets .is-focused (left accent stripe + soft pink background) so Enter commits the most-likely action.
<div class="cmdk" role="dialog" aria-label="Command palette">
<div class="cmdk-input-wrap">
<svg ...search-icon.../>
<input class="cmdk-input" placeholder="Find anything...">
<span class="cmdk-kbd">esc</span>
</div>
<div class="cmdk-results" role="listbox">
<div class="cmdk-group">
<div class="cmdk-group-label">Recent</div>
<button class="cmdk-row is-focused" role="option">
<span class="cmdk-row-icon">...</span>
<span class="cmdk-row-body">
<span class="cmdk-row-title">BlueRock Health · Renewal Q2</span>
<span class="cmdk-row-sub">Deal · $48k · Negotiation</span>
</span>
<span class="cmdk-row-meta">↩</span>
</button>
...more rows...
</div>
<div class="cmdk-group">...Actions group...</div>
</div>
<div class="cmdk-foot">
<kbd>↑</kbd><kbd>↓</kbd>navigate · <kbd>↩</kbd>open · <kbd>esc</kbd>close
</div>
</div>.cmdk { background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: var(--r-lg); box-shadow: var(--sh-3);
max-width: 560px; overflow: hidden; }
.cmdk-row { display: grid;
grid-template-columns: 28px 1fr auto;
gap: var(--s-3); padding: 8px var(--s-5);
cursor: pointer; background: transparent; border: 0; }
.cmdk-row:hover, .cmdk-row.is-focused {
background: var(--accent-soft); }
.cmdk-row.is-focused { box-shadow: inset 3px 0 0 var(--accent); }
/* In real use, debounce input by 80ms; pin recent items
when input is empty; fuzzy-match on type. */13.3 Density mode
A global toggle (admin-set default + per-user override) that shrinks row heights by 16px, card paddings by 4px, small captions by 1px. Implemented via body[data-density="compact"] overriding --row-h from comfortable (52px) to compact (36px). Components reference var(--row-h) so they switch automatically. The demo below scopes the toggle to one wrapper so you can see both modes side-by-side.
Comfortable vs compact
.density-row · --row-hToggle the chip below to compare. The same row markup honours both densities — only the row height (and proportional padding scale) changes. Compact is for power users who want more rows on screen; comfortable is the default for everyone else.
data-density on the demo wrapper below; in real use, set on <body>.
<!-- Toggle UI (settings panel or per-user pref) -->
<div class="density-toggle" role="group">
<button class="is-active" data-density-set="comfortable">Comfortable</button>
<button data-density-set="compact">Compact</button>
</div>
<!-- Components reference var(--row-h); when body[data-density="compact"]
is set, --row-h flips from --row-h-comfortable to --row-h-compact -->
<div class="density-row">
<span class="av">AC</span>
<div>...</div>
<span>2h</span>
</div>/* In components/_shared.css (Phase 1 1A): */
:root {
--row-h-comfortable: 52px;
--row-h-compact: 36px;
--row-h: var(--row-h-comfortable);
}
body[data-density="compact"] {
--row-h: var(--row-h-compact);
}
/* In any component CSS: */
.density-row { height: var(--row-h, var(--row-h-comfortable)); }
/* JS to drive the toggle: */
document.querySelectorAll('[data-density-set]').forEach(b => {
b.addEventListener('click', () => {
document.body.dataset.density = b.dataset.densitySet;
localStorage.setItem('mb-density', b.dataset.densitySet);
});
});13.4 Empty states
Five canonical CRM-flavoured empty states with on-brand line-drawn illustrations. Every absent surface gets one of these — never leave a list, table, or feed blank. Anatomy: small illustration · headline · 1-line lede · primary CTA + secondary “Import” link.
No contacts · No deals · No tickets · No KB · No partners
.empty-cardEach lede is short and warm. The CTA is the single most-likely action; the secondary link is the fallback (usually “Import”). Illustrations are pure SVG — ink + accent only, no gradients.
Bring some people in.
Your contacts list is empty. Import from a CSV or add your first contact directly.
Your pipeline is hungry.
No deals yet. Add one manually or pull them in from a connected source.
Nice — no fires today.
The ticket queue is empty. Customers are probably happy, or your team is on top of it.
Teach Sage what you know.
No knowledge base articles yet. Sage gets sharper with every doc you add.
Find a few good partners.
No partners in your network yet. Invite an agency, integrator, or reseller to get started.
<div class="empty-card">
<div class="empty-illo" aria-hidden="true">
<svg viewBox="0 0 96 96">...line-drawn illustration...</svg>
</div>
<h3>Bring some <em>people</em> in.</h3>
<p>Your contacts list is empty. Import from a CSV
or add your first contact directly.</p>
<div class="empty-actions">
<button class="empty-cta">+ New contact</button>
<a class="empty-link" href="#">Import CSV</a>
</div>
</div>.empty-card { background: var(--bg-paper);
border: 1px solid var(--hair); border-radius: var(--r-lg);
padding: var(--s-7) var(--s-5); text-align: center;
display: flex; flex-direction: column; align-items: center; gap: var(--s-3); }
.empty-card .empty-illo { width: 96px; height: 96px; }
.empty-card h3 { font: 700 18px var(--f-display); }
.empty-card h3 em { font-family: var(--f-serif); font-style: italic;
color: var(--accent-text); font-variation-settings: "SOFT" 80; }
.empty-card .empty-cta { background: var(--accent); color: var(--paper);
padding: 10px 18px; border-radius: var(--r-pill);
box-shadow: var(--sh-pink); }