Design Review · 2026-05-01

Agency OS UI Review · Before & After

Eleven prioritized fixes from the foundation audit, shown side by side. Each "after" follows the rules captured in PRODUCT.md and DESIGN.md (restrained color, no side-stripe, no hero-metric template, paired display font, motion under 250ms with exponential ease-out).

01 P1 · Distill

Sidebar: kill the 3px side-stripe on active nav

The 3px border-left is an impeccable absolute ban. Replaced with a full-width tinted pill at brand-primary. Active state is unmistakable, the chrome is quieter, and the rule lives in one place.

Before
Overview
Clients
Reporting
Pipeline Health
Settings

DashboardLayout.tsx:137: border-l-[3px] on active. Side-stripe pattern is in active use across the codebase.

After
Overview
Clients
Reporting
Pipeline Health
Settings

Full-width pill at --brand-primary, white label and icon. No left edge accent. Reads as "selected", not "decorated".

02 P1 · Bolder

Overview KPIs: break the hero-metric template

Four identical cards in grid-cols-2 lg:grid-cols-4 is the SaaS cliché impeccable specifically bans. The "after" elevates one anchor metric (Spend, with sparkline), groups the rest into a dense companion strip, and rolls the smaller diagnostics into an inline row. Same data, real hierarchy.

Before

Total Spend

$248,901
+12.4%

Conversions

3,124
+4.2%

Impressions

1.8M
+8.1%

Clicks

42,107
+6.3%

OverviewPage.tsx:379. Four equal cards. No anchor, no rhythm, no story. Same layout reused on ReportingPage.

After

Total Spend · MTD

$248,901
+12.4% vs prev period

Conversions

3,124

Revenue

$612K

Impressions

1.8M

Clicks

42.1K

CTR

2.34%

One anchor metric earns the size. Two supporting metrics ride alongside. Diagnostics fold into one row. Visual hierarchy carries the answer to "are we on track" in one glance.

03 P1 · Typeset

Typography: pair Inter with a display font

Inter everywhere is the #1 AI-slop font tell. Pair it with a distinctive display face for headings (sample: Fraunces, a humanist serif with optical sizing). Body stays Inter. Hierarchy now carries character, not just scale. If the user does not have Fraunces installed locally, the system fallback (Iowan Old Style on macOS, Georgia elsewhere) still demonstrates the contrast.

Before

Performance, this month

Spend is up 12% vs last period. Two clients are pacing behind. One campaign is over budget by 8%.

Inter at all weights. Hierarchy via size only. Reads as default-ish, indistinguishable from any 2024 SaaS dashboard.

After

Performance, this month

Spend is up 12% vs last period. Two clients are pacing behind. One campaign is over budget by 8%.

Display heading in a humanist serif (Fraunces / Iowan Old Style fallback). Body remains Inter. Heading carries voice; body stays neutral. The two together feel intentional.

04 P1 · Animate

Motion: modal entry & ProgressDot color transition

Two motion gaps in one. Top: 9 modals snap in with no enter / exit; "after" uses a 200ms scale + fade with exponential ease-out (the existing dropdown-in keyframe at index.css:61-64). Bottom: ProgressDot color flashes between threshold states with no transition; "after" tweens fill / stroke over 320ms.

Before

Modal pops in instantly · 0ms transition

Pacing dot: snaps between colors

9 modals (Connect, Welcome, CreateTarget, ManageMembers, InviteClient, CustomKpiBuilder, GenerateReport, ScheduleReport, SetOrgTargets) and ProgressDot SVG fill changes are all instant.

After

200ms scale (0.96 → 1) + fade · ease-out-expo

Pacing dot: smooth fill transition

Modal: scale(0.96) → scale(1) + fade-in over 200ms. ProgressDot: transition: fill 320ms, stroke 320ms on the SVG primitives. Both watch the loop animation above to compare.

05 P1 · Harden

Heading hierarchy: stop styling divs as section titles

Most pages have one h1, then everything below is a styled div. Screen readers can't navigate by headings; SEO does not see structure; Reader Mode breaks. Same visual look, semantic markup.

Before
<h1> Good morning, Sourabh
<div class="text-sm font-bold"> Key Metrics
<div class="..."> Top Campaigns
<div class="..."> Activity Log

Section titles are divs with title classes. Outline view (browser dev tools): one h1, no h2, no h3.

After
<h1> Good morning, Sourabh
<h2> Key Metrics
<h2> Top Campaigns
<h3> This week
<h2> Activity Log

Same visual treatment via Tailwind classes; semantic h2/h3 underneath. Reader Mode works. Skip-to-section keyboard navigation works.

06 P2 · Layout

Reporting page: differentiate visual rhythm from Overview

Currently the Reporting page reads as "Overview with filters". Same 4-col KPI grid, same density, same composition. The "after" introduces variation: a wide hero band on top, a 3-1 split row, then a 1-2 row. The page now has its own shape.

Before

Four equal columns × four equal rows. Monotone. Indistinguishable from Overview.

After

Hero row (2-1-1) anchors the page. Wide chart band (3-1) follows. Filter strip (1-2) introduces a different scan rhythm. Reads as a different page.

07 P2 · Adapt

Touch targets: 44 by 44 minimum

Sidebar collapse uses p-1.5, ≈ 24×24px including the icon. Below WCAG 2.5.5 minimum and frustrating on tablet. The "after" pads the hit area to 44×44 without changing the icon size.

Before
24 × 24 px · target ≪ hit area

DashboardLayout.tsx:107: p-1.5. Misses on every other tap on iPad. Coral dashed line shows the visible target; user taps land outside.

After
44 × 44 px · WCAG 2.5.5 AA

Hit area expanded to 44×44 via padding. Icon stays at 14px. Apply same fix to nav items at narrow breakpoint.

08 P2 · Colorize

Status colors: move hard-coded hex to CSS variables

Code-only fix; no visual change for end users, but a structural one. #f87171, #fbbf24, #4CAF50 appear inline in ProgressDot.tsx and elsewhere. Centralizing them lets dark mode, white-label, and accessibility overrides all swap in one place.

Before
// ProgressDot.tsx:17-20
const dotColor = (pct, brand) => {
  if (pct < 50) return '#f87171';
  if (pct < 75) return '#fbbf24';
  return brand;
};

Hex literals in three components. A status color change requires editing every site of use.

After
// index.css
--color-status-behind: #F87171;
--color-status-near:   #FBBF24;
--color-status-ahead:  var(--color-primary);

// ProgressDot.tsx
const dotVar = (pct) => {
  if (pct < 50) return 'var(--color-status-behind)';
  if (pct < 75) return 'var(--color-status-near)';
  return 'var(--color-status-ahead)';
};

One source of truth. Dark mode and per-deployment color tweaks now possible without code changes.

09 P2 · Harden (a11y)

prefers-reduced-motion: extend coverage to Tailwind transitions

Current rule covers custom keyframes (shimmer, bar-bounce, dropdown-in) but not the transition-* Tailwind utilities sprinkled on every button, chevron, and modal. A vestibular-sensitive user still sees animation.

Before
/* index.css:81-87 */
@media (prefers-reduced-motion: reduce) {
  .animate-dropdown,
  .animate-toast,
  .loading-bars span,
  .skeleton {
    animation: none !important;
  }
}
/* button transitions, chevron rotates,
   modal backdrop fades all still run */

Partial. A user with motion sensitivity still sees all 150ms hover and chevron animations.

After
/* index.css */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Universal selector neutralizes both keyframes and transitions. The 0.01ms duration (rather than 0) preserves event firing for code that depends on transitionend.

10 P3 · Delight

Empty states: one spark of personality

"No team members assigned" is technically correct and emotionally flat. The "after" gives the empty state weight: a tinted icon block, display-font heading, a sentence with character, and a clear next action. Jhey-lens delight without crossing into mascot territory.

Before

No team members

No team members assigned

ClientDetailPage.tsx:387-390. Restates the heading. No path forward. No voice.

After

Nobody's looking after this client yet

Assign an account manager so they get the right alerts and pacing reports.

Assign team member

Heading does not restate the title. Sentence explains the consequence. Icon has dimension. CTA is the obvious next step.

11 Polish

Final pass: cumulative effect

After 1 through 10 land, run a polish sweep. The list below is what changes about the product's character, not its features. The score moves from 13 / 20 (Acceptable) toward 17 / 20 (Good).