Practice · Design
Portal UX Principles
Last updated: June 2025
Twelve decisions that shape how this portal feels — and why each one was made that way. A reference for when something feels off, or when something new needs to fit in.
Structure
How the portal is organized
01
All pages
Two rooms, one portal
The portal has two distinct modes — Personal (your day, your attention, your rhythm) and Practice (pipeline, clients, work being done). These are not tabs. They are rooms with different purposes, different data, and different emotional registers.
Every page header carries a room label in its eyebrow: "Personal view", "Practice · [page]", or "Workspace · [page]". Never mix signals — a pipeline view is never "Personal".
Eyebrow encoding
Personal view · Wednesday
Good morning.
Practice · Pipeline
Pipeline
Active and prospective engagements.
02
All pages
pv-header is the only allowed entry point
Every view starts with a pv-header block: eyebrow → title → sub, always in that order. No page begins with content. The header establishes where you are, what room you're in, and what the page is for before anything else appears.
Structure: .pv-eyebrow (Courier New, 10px, ochre) → .pv-title (Spectral, 32px, weight 300) → .pv-sub (14px, italic, ink3). Action buttons sit at the right edge of the title row, never below.
03
Panels · Drawers
Panels, not pages — detail never navigates away
When a user clicks a pipeline entry, a contact, or a decision, the detail opens in a side drawer — not a new page. Context is preserved. The list stays visible. Closing the panel returns focus to exactly where it was.
Use #sidePanel with the openPanel(id) / closePanel() pattern. Panel width is set per-view via CSS variable. Wizard overlays are the only exception — full-screen for multi-step flows.
Panel anatomy
Contact detail
✕ Close
Content, notes, and actions — without leaving the contacts view.
Typography
The two-typeface rule
04
Type · All pages
Spectral for weight — Courier for precision
Spectral (serif, weight 300) carries all titles, headers, and large display text. It reads as considered, unhurried, human. JetBrains Mono / Courier New carries all labels, eyebrows, metadata, filter chips, and system strings. It reads as factual, exact, machine-legible. These two voices do not cross.
Never use Spectral for a label. Never use Barlow or sans-serif for a page title. If something feels off, check the typeface first — it's almost always a Barlow 700 h1 that slipped through from v3.
05
Headers · pv-sub
pv-sub is the contract
The italic subtitle under every page title is not decoration — it states what the page is for in one sentence. It sets scope. If a user adds a feature that doesn't fit the subtitle, the subtitle is wrong and must be updated. The subtitle is the first check for scope creep.
Max one sentence. Plain declarative. No colons, no lists. Example: "Active and prospective engagements, by stage and owner." Not: "Pipeline management, health tracking, stage progression, and revenue forecasting."
Color
What each color means
06
Color · Ochre
Ochre is the active channel — never decorative
Ochre (--amber: #A8621E) means one thing: this is live, selected, or requires your attention. It is used for active nav items, filter chips in the "on" state, eyebrow text, and attention-level highlights. Using ochre for decorative borders or section breaks dilutes its signal.
If you want to add visual warmth, use --amber-bg (the pale fill) without the text color. Full ochre text is reserved for interactive or live-state elements only.
Correct use — filter state
Active
Building
Forming
Holding
07
Color · Confidence
Confidence before certainty — always show the signal strength
Any AI-generated content, inferred value, or system-calculated field carries a confidence pill. Green = HIGH (verified or strongly evidenced), Ochre = MED (inferred from partial data), Red = LOW (speculative). Users should never wonder whether a number is real or guessed.
Use .conf-pill--high / --med / --low. The pill sits immediately next to the value, never in a tooltip. Tooltips require hovering. Confidence requires no interaction.
Confidence pill states
HIGH
MED
LOW
Interaction
How things respond
08
Filters · All pages
Filter chips, not dropdowns — state is always visible
Filter controls are horizontal pill rows. The active state is visible at all times — no opening, no closing, no hidden state. A user can see at a glance what filters are applied. Dropdowns hide state. Chips expose it.
Use .filter-chip (from ds.css). Active state is .on (ochre fill, white text). Never use .active — that class is reserved for nav items. The .filter-bar wrapper handles horizontal scroll on mobile.
09
Search · ⌘K
Entity tokens over text search — pin first, then verb
The ⌘K command bar detects when a query resolves to a single entity (a contact or pipeline entry). When it does, it offers to pin that entity as a token. The token stays visible in the input bar and scopes all subsequent commands to that entity. This turns a search tool into a focused workspace.
Token detection threshold: top hit label starts with the query text, length within 6 chars. Pin button shows "Pin ◉". Token rendered in ochre pill with .ck-token. Backspace clears the token when input is empty.
Entity token in command bar
Paul Hawken ×
log a call…
10
Data display
Stage cards, not tables — counts get Spectral weight
KPI numbers are displayed as stage cards: a large Spectral 300 count over a mono label, in a horizontal strip. This gives numbers room to breathe and makes them scannable at distance. Tables are reserved for detailed record lists — never for summary statistics.
Use .stage-strip + .stage-card from ds.css. The count uses .stage-card-count (Spectral 26px 300). Do not use inline styles for these — the classes are the spec.
Stage strip
Total
24
Active
6
Prospecting
11
Won
7
System
How the design system works
11
CSS · ds.css
ds.css loads last and always wins
Every portal page loads limicelia-ds.css as the final <link> in the <head>, after all local <style> blocks. This means ds.css always overrides local CSS for any class it defines. Local styles are for layout only — never redefine --amber, .btn, .filter-chip, or any ds.css class locally.
If you need a one-off variant, add a modifier class (e.g. .pv-header--flush) to ds.css. Never override a ds.css class with inline styles or local style blocks. The token layer must stay coherent across all pages.
12
Mobile · Responsive
Mobile: collapse padding, not content
At ≤680px, page headers shrink their padding and font sizes but display the same content. Nothing is hidden on mobile except explicitly optional chrome (sidebar rail, layout toggles). The eyebrow, title, and subtitle remain visible. Action buttons stay in the header row — they do not collapse into a menu.
The ds.css @media (max-width: 680px) rule handles .pv-header: padding reduces from 28/32 to 16/18, title from 32px to 24px. Pages should not add their own breakpoints for pv-header — the system handles it.