Browse documentation

Frontend Specs

RankFlow AI — Frontend Specification

Document Version: 1.0.0

docs/specs/frontend/frontend-spec-01-design-system.md
On this page

Section 1: Design System & Global Foundations#

Document Version: 1.0.0
Date: 2026-01-15
Owner: Frontend Engineering
Scope: All client-facing and admin-facing UI components, screens, and interactions
Format: Markdown (.md) — primary deliverable for version control and diffing
Tech Stack: Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS, tRPC, React Query (TanStack Query), Zod, Better Auth, Recharts, date-fns, Lucide React


1.1 Document Overview#

This specification acts as the single source of truth for all frontend implementation. Every screen, widget, pop-up, interaction flow, and data wire is documented with exact backend endpoint names, fetch frequencies, display patterns, and manipulation rules. Frontend coding agents must follow this document with zero ambiguity.

Critical constraints (non-negotiable):

  • NO bold font weight. Use font-weight: 500 (Medium) as the heaviest weight. Suisse Intl Medium is the max.
  • Ember Orange #EC652B is ONLY for: primary action buttons, CTA links, focus rings, and positive outcome indicators. Never for text, backgrounds, error states, or status badges.
  • Page max-width: 1200px centered. Canvas background: Paper White #F6F6F8.
  • Sidebar: 240px wide, Deep Ink #011821 background, always visible on desktop, collapses to hamburger on mobile.
  • Section gap: 80px between major sections.
  • Card radius: 8px (border-radius-lg).
  • Medical compliance: All content for medical clients (CLINIC/HOSPITAL/DOCTOR) goes through 24h approval queue. No exceptions.

1.2 Design Token System#

All tokens are consumed via Tailwind CSS config (tailwind.config.ts) and exposed as CSS custom properties in :root.

1.2.1 Color Palette#

Token Name Hex Value Usage Restriction
paper-white #F6F6F8 Primary canvas, page background Never use for text on light surfaces
card-white #FFFFFF Cards, dialogs, inputs, elevated surfaces Default surface for all cards
deep-ink #011821 Primary text, dark surfaces, sidebar bg Never use #000000
carbon #12161E Secondary dark panels, admin dense tables Technical dark surfaces
deep-indigo #111A4A Charts, data series, dark accents Supporting only
midnight-teal #023247 Info states, technical panels, hover bg Primary button hover
ember-orange #EC652B CTA ONLY — primary action, focus, booked NEVER for text, backgrounds, or status
forest-teal #167E6C Success, verified, live states, positive data Success badges, checkmarks
sky-blue #7EA7E9 Informational data series, secondary charts Data viz only
pale-cyan #C1E8EF Info backgrounds, selected states, soft alerts Light backgrounds only
mint #44B48B Secondary positive data, illustration Supporting green
lavender #9F7AEE Secondary data series only Data viz only
mist #E3E4E8 1px borders, separators, rules All borders default
fog #A9ACB6 Disabled controls, non-essential decoration Disabled states only
slate #7C7F88 Tertiary labels, muted metadata Captions, timestamps
graphite #3B3E47 Secondary text, icons, body text Body text on light surfaces
error-red #C2442A Destructive actions, errors, failed states Cancel, delete, error alerts

Semantic aliases (use these in components, not raw hex):

Semantic Token Maps To Usage
text-primary deep-ink Headings, primary body text
text-secondary graphite Body text, descriptions
text-tertiary slate Timestamps, metadata, captions
text-disabled fog Disabled labels, placeholder text
text-inverse paper-white Text on dark backgrounds
surface-canvas paper-white Page background
surface-card card-white Card, modal, input backgrounds
surface-dark deep-ink Sidebar, dark panels
border-default mist All default borders
action-primary deep-ink Primary button bg
action-accent ember-orange CTA button bg only
success forest-teal Success states
warning ember-orange Warning states (same as accent)
error error-red Error states
info midnight-teal Info states

1.2.2 Typography#

Font family: Suisse Intl primary. Fallback stack: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif.
Monospace: Suisse Intl Mono'SFMono-Regular', Menlo, Monaco, Consolas, monospace.

Token Size Line Height Letter Spacing Weight Usage
display 60px 66px -0.03em 500 Hero headlines only
heading-lg 40px 44px -0.02em 500 Page titles, section headers
heading 28px 36px -0.02em 500 Card titles, modal headers
heading-sm 24px 32px -0.02em 500 Sub-section headers
subheading 20px 28px -0.01em 500 Card subtitles, list headers
body-lg 18px 26px -0.01em 400 Lead paragraphs, descriptions
body 16px 24px 0 400 Default body text
body-sm 14px 21px 0 400 Secondary text, form labels
caption 12px 18px 0.01em 400 Timestamps, badges, metadata

Weight rules:

  • 400 (Regular): All body text, descriptions, labels, inputs.
  • 500 (Medium): Headings, buttons, navigation, active states, emphasis.
  • NO 600 or 700 on any text. font-semibold and font-bold are banned.
  • 300 (Light): Optional for hero subtext or large display numbers.

1.2.3 Spacing Scale#

Base unit: 4px. All spacing values are multiples of 4px.

Token Value Usage
space-1 4px Micro gaps, icon padding
space-2 8px Tight element gaps, inline spacing
space-3 12px Default element gap, form field spacing
space-4 16px Card internal padding (compact)
space-5 20px Button padding, input padding
space-6 24px Standard card padding, modal padding
space-8 32px Section internal padding, grid gaps
space-10 40px Between card groups, form sections
space-12 48px Between major UI regions
space-16 64px Between page sections
space-20 80px Between major page sections (hero to content)
space-24 96px Large page spacing

Layout tokens:

  • page-max-width: 1200px (centered with mx-auto).
  • section-gap: 80px (between major sections).
  • card-padding: 24px (internal card padding).
  • element-gap: 12px (between sibling elements in a card).

1.2.4 Border & Radius#

Token Value Usage
radius-none 0 Tables, data grids
radius-sm 2px Small tags, compact badges
radius-lg 8px Cards, buttons, inputs, modals — default
radius-xl 12px Large cards, feature sections
radius-full 9999px Pills, avatars, status badges
border-default 1px All default borders (mist color)
border-strong 2px Focus states, active selections

1.2.5 Shadows#

Token Value Usage
shadow-subtle 0 1px 3px rgba(17,26,74,0.10), 0 1px 0 rgba(17,26,74,0.05), inset 0 1px 0 rgba(255,255,255,0.50), inset 0 1px 4px rgba(255,255,255,0.50) Card default
shadow-outline 0 0 0 1px rgba(87,90,100,0.12) Subtle card borders
shadow-sm 0 4px 8px rgba(0,0,0,0.05), 0 2px 4px rgba(0,0,0,0.10), 0 1px 1px rgba(0,0,0,0.10) Elevated cards, dropdowns
shadow-xl 0 40px 32px rgba(0,0,0,0.02), 0 22px 18px rgba(0,0,0,0.03), 0 12px 10px rgba(0,0,0,0.03), 0 7px 5px rgba(0,0,0,0.04), 0 3px 2px rgba(0,0,0,0.07) Modals, dialogs
shadow-focus 0 0 0 3px rgba(236,101,43,0.28) Focus rings (ember-orange)

1.2.6 Motion#

Token Value Usage
duration-instant 100ms Hover color changes, opacity
duration-fast 150ms Button presses, icon toggles
duration-base 200ms Card transitions, dropdowns, modals
duration-slow 300ms Page transitions, sidebar collapse
easing-standard cubic-bezier(0.2, 0, 0, 1) Default easing
easing-enter cubic-bezier(0, 0, 0, 1) Entering elements
easing-exit cubic-bezier(0.3, 0, 1, 1) Exiting elements

Animation rules:

  • All transitions use duration-base (200ms) unless specified otherwise.
  • Modal enter: duration-slow + easing-enter. Exit: duration-fast + easing-exit.
  • Sidebar collapse: duration-slow + easing-standard.
  • Skeleton shimmer: duration-slow infinite loop, translateX animation.
  • Toast enter: slide-in from bottom + fade, duration-base. Exit: fade-out, duration-fast.
  • Reduced motion: respect prefers-reduced-motion: reduce — disable all non-essential animations.

1.2.7 Z-Index Scale#

Token Value Usage
z-base 0 Default stacking
z-dropdown 100 Select menus, dropdowns
z-sticky 200 Sticky headers, sidebars
z-fixed 300 Fixed navbars
z-modal-backdrop 400 Modal overlay
z-modal 500 Modal content
z-popover 600 Popovers, tooltips
z-tooltip 700 Tooltips (above popovers)
z-toast 800 Toasts (topmost)

1.3 Global Component Patterns#

Every component documented below follows the 10-point specification template. This section defines the base patterns; all screen-specific components inherit from these.

1.3.1 Button#

Variants:

Variant Background Text Border Hover BG Hover Border Usage
Primary deep-ink paper-white none midnight-teal none Main action on page
Accent ember-orange deep-ink none #D95A23 (darkened 10%) none CTA only — conversion actions
Secondary card-white deep-ink mist 1px rgba(1,24,33,0.04) fog Alternative actions
Ghost transparent deep-ink mist 1px rgba(1,24,33,0.04) fog Low-emphasis actions
Danger error-red paper-white none #A8321F (darkened 15%) none Destructive actions
Link transparent midnight-teal none transparent none Text links, underline on hover

Specs:

  • Padding: 10px 16px (compact: 8px 12px).
  • Radius: 8px (radius-lg).
  • Font: body-sm (14px), weight 500.
  • Icon + text: icon left, space-2 (8px) gap. Icon size: size-md (20px).
  • Loading state: spinner replaces icon, disabled opacity 0.6, cursor not-allowed.
  • Disabled state: fog background, slate text, no hover effect.
  • Focus: shadow-focus ring (ember-orange, 3px).
  • Width: auto (hug content) by default. Full-width variant for mobile.

States:

  • Default → Hover (150ms, color transition).
  • Pressed → Active (scale 0.98, 100ms).
  • Loading → Spinner rotation, disabled pointer events.
  • Disabled → Opacity 0.6, no interaction.

1.3.2 Input#

Types: text, email, password, number, tel, url, textarea, select, date, file.

Specs:

  • Background: card-white.
  • Border: mist 1px, radius 8px.
  • Padding: 10px 12px.
  • Font: body (16px), weight 400, color text-primary.
  • Placeholder: graphite, weight 400.
  • Focus: border ember-orange, shadow-focus ring.
  • Error: border error-red, error message below in caption (12px), color error-red.
  • Disabled: background paper-white, text fog, border mist.
  • Label: body-sm (14px), weight 500, color text-primary, space-1 (4px) margin-bottom.
  • Helper text: caption (12px), color text-tertiary, space-1 margin-top.
  • Icon left: space-3 (12px) left padding increase, icon color slate.

Validation:

  • Real-time on blur (not on every keystroke to reduce noise).
  • Zod schema validation via tRPC error responses.
  • Error state persists until corrected.

1.3.3 Card#

Variants: metric, content, action, data.

Specs:

  • Background: card-white.
  • Border: mist 1px or shadow-outline (no border + outline shadow).
  • Radius: 8px.
  • Padding: 24px (space-6).
  • Shadow: shadow-subtle (default), shadow-sm (elevated/hover).
  • Hover: shadow-sm, translateY(-1px), 200ms transition.
  • Internal layout: header (title + action), body (content), footer (metadata/actions). Gap: space-3 (12px) between regions.

Metric Card (special):

  • Header: label (caption, text-tertiary), optional trend badge.
  • Body: value (heading or heading-lg, weight 500), change indicator (arrow + %, color-coded).
  • Footer: sparkline (mini line chart, 30px height) or comparison text.
  • Padding: 20px (compact).

1.3.4 Table#

Specs:

  • Header: background paper-white, text caption (12px), weight 500, uppercase, color text-tertiary, border-bottom mist 1px.
  • Row: background card-white, border-bottom mist 1px, hover row-hover (rgba(1,24,33,0.02)).
  • Cell padding: 12px 16px.
  • Radius: 8px on outer corners (container), rows have no internal radius.
  • Sortable header: clickable, sort icon (arrow up/down), active sort: text-primary + icon color ember-orange.
  • Pagination: below table, space-4 (16px) margin-top. Page size: 20/50/100.
  • Empty state: centered icon + text + CTA button, space-12 (48px) padding.
  • Loading: skeleton rows (5 rows, pulsing paper-white to mist).
  • Selection: checkbox leftmost column, batch action bar appears above table when items selected.

1.3.5 Modal / Dialog#

Specs:

  • Backdrop: rgba(1, 24, 33, 0.48), z-modal-backdrop, click to close (unless critical).
  • Container: card-white, radius-lg, shadow-xl, max-width: 560px (small), 720px (medium), 960px (large), 1200px (xl).
  • Padding: 24px (space-6).
  • Header: title (heading-sm, 24px, weight 500), close button (X icon, top-right).
  • Body: scrollable if content exceeds 70vh.
  • Footer: action buttons (right-aligned, primary on right), space-4 (16px) gap.
  • Animation: enter (fade-in + scale 0.95→1, 200ms), exit (fade-out + scale 1→0.95, 150ms).
  • Focus trap: first focusable element focused on open. Return focus on close.
  • Scroll lock: body scroll disabled when modal open.

Types:

  • Confirmation: Title, description, primary action (danger or accent), secondary cancel.
  • Form: Title, form fields, validation, primary submit, secondary cancel.
  • Detail: Title, read-only content, close button, optional edit action.
  • Slide-over: Right-aligned panel (max-width 480px), used for filters, details, creation flows. Full height, slide-in from right (300ms).

1.3.6 Toast / Notification#

Specs:

  • Position: bottom-right (desktop), bottom-center (mobile), z-toast.
  • Container: card-white, radius-lg, shadow-sm, max-width 400px.
  • Padding: 12px 16px.
  • Icon left: status icon (check, alert, info, error), color-matched.
  • Content: title (body-sm, weight 500), description (caption, optional).
  • Close button: X icon, right side.
  • Auto-dismiss: success 3000ms, info 5000ms, warning 5000ms, error 8000ms (or manual close).
  • Animation: enter (slide-up + fade, 200ms), exit (fade-out, 150ms).
  • Max stacked: 5 toasts. Newest at bottom.
  • Progress bar: thin line at bottom, shrinks during auto-dismiss timer.

Status colors:

  • Success: icon forest-teal, border-left forest-teal 3px.
  • Warning: icon ember-orange, border-left ember-orange 3px.
  • Error: icon error-red, border-left error-red 3px.
  • Info: icon midnight-teal, border-left midnight-teal 3px.

1.3.7 Badge / Status Tag#

Specs:

  • Radius: full (pill shape).
  • Padding: 4px 12px.
  • Font: caption (12px), weight 500.
  • Icon optional: left of text, space-1 gap.

Variants:

Status Background Text Icon Usage
Success rgba(22,126,108,0.12) forest-teal Check circle Completed, active, verified
Warning rgba(236,101,43,0.12) ember-orange Alert triangle Pending, warning, needs attention
Error rgba(194,68,42,0.12) error-red X circle Failed, error, rejected
Info rgba(2,50,71,0.12) midnight-teal Info circle Info, draft, scheduled
Neutral rgba(1,24,33,0.06) graphite Dot Default, inactive
Processing rgba(159,122,238,0.12) lavender Loader Running, syncing, generating

1.3.8 Sidebar Navigation#

Specs:

  • Width: 240px (desktop), 64px (collapsed), full-width overlay (mobile).
  • Background: deep-ink (#011821).
  • Text: paper-white primary, fog muted.
  • Active item: rgba(246,246,248,0.12) background, left border ember-orange 3px, text paper-white weight 500.
  • Hover item: rgba(246,246,248,0.08) background, 150ms transition.
  • Item padding: 10px 16px.
  • Icon: size-md (20px), left of label, space-3 gap.
  • Group header: caption (12px), uppercase, fog, space-4 margin-top.
  • Collapse toggle: bottom of sidebar, chevron icon, 200ms rotation animation.
  • Mobile: hamburger menu in top nav, slide-in from left, backdrop overlay.
  • Logo area: top of sidebar, space-6 padding, logo + app name.

Navigation items (client):

  1. Home (/dashboard) — LayoutDashboard icon
  2. GBP (/dashboard/gbp) — MapPin icon
  3. Social (/dashboard/social) — Share2 icon
  4. Citations (/dashboard/citations) — Link icon
  5. Site (/dashboard/site) — Globe icon
  6. Content (/dashboard/content) — FileText icon
  7. Leads (/dashboard/leads) — Users icon
  8. Reports (/dashboard/reports) — BarChart3 icon
  9. Settings (/dashboard/settings) — Settings icon

Navigation items (admin):

  1. KPI Overview (/admin/kpi) — Activity icon
  2. Clients (/admin/clients) — Users icon
  3. Infrastructure (/admin/infrastructure) — Server icon
  4. Content (/admin/content) — FileText icon
  5. Billing (/admin/billing) — CreditCard icon
  6. Reports (/admin/reports) — BarChart3 icon

1.4 Auth State Management#

1.4.1 Session Architecture#

Better Auth integration:

  • Session stored in HTTP-only cookie (__session), not localStorage.
  • Session refresh: automatic via Better Auth middleware, 30-day expiry, sliding window.
  • Role decoded from JWT claim: role: "ADMIN" | "CLIENT" | "EDITOR" | "VIEWER".
  • User object shape:
    interface User {
      id: string;
      email: string;
      name: string;
      role: "ADMIN" | "CLIENT" | "EDITOR" | "VIEWER";
      practiceId: string | null;
      emailVerified: boolean;
      mfaEnabled: boolean;
      image?: string;
    }
    

1.4.2 tRPC Context#

  • All tRPC queries/mutations include session via ctx.session.
  • Auth middleware stack (order matters):
    1. authMiddleware — validates session, attaches user to ctx.
    2. roleMiddleware — checks role against route requirements.
    3. auditMiddleware — logs action with user ID, timestamp, route.
    4. impersonationMiddleware — if impersonating: true in session, restricts to read-only for most operations.

1.4.3 Role-Based Access Control (RBAC)#

Role Access Level Description
ADMIN Full system access All routes, all operations, impersonation, system config
CLIENT Practice owner Full dashboard access, billing, team management, all actions
EDITOR Content manager All dashboard except billing/team, can approve/reject content
VIEWER Read-only observer View all data, no actions, no edits, no approvals

Route guards:

  • /dashboard/*: CLIENT, EDITOR, VIEWER allowed. ADMIN redirected to /admin.
  • /admin/*: ADMIN only. Non-ADMIN get 403 redirect to /dashboard.
  • /onboarding: Only during onboarding flow. Post-completion redirect to /dashboard.
  • /: Public (marketing). Auth state shows "Dashboard" or "Sign In" in nav.
  • /login, /signup: Public. Authenticated users redirected to /dashboard (or /admin for ADMIN).

Permission checks (client-side):

const canEdit = user.role === "CLIENT" || user.role === "EDITOR";
const canManageBilling = user.role === "CLIENT";
const canApprove = user.role === "CLIENT" || user.role === "EDITOR";
const isAdmin = user.role === "ADMIN";

UI enforcement:

  • Hidden buttons/links for disallowed actions (not disabled — hidden to reduce clutter).
  • Disabled state only used when action is conditionally unavailable (e.g., "Approve" disabled if already approved).
  • Admin impersonation: red banner at top of all pages: Impersonating [Client Name] — [time remaining]. All actions logged with impersonating: true. 2-hour timeout. Exit impersonation button (X) in banner.

1.4.4 Data Fetching Patterns#

tRPC Client Setup:

  • React Query (@tanstack/react-query) provider wraps app.
  • Default query options:
    const defaultQueryOptions = {
      staleTime: 60 * 1000,      // 60 seconds
      cacheTime: 5 * 60 * 1000,  // 5 minutes
      refetchOnWindowFocus: true,
      refetchOnReconnect: true,
      retry: 2,                  // 2 retries on failure
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    };
    

Query Categories by Refresh Rate:

Category Examples StaleTime CacheTime Refetch Interval Polling
Real-time Job status, queue depth 0 1 min 10 seconds Yes
Frequent KPIs, reviews, leads, content queue 60s 5 min 60 seconds Yes
Standard Charts, posts, pages, redirects, prompts 5 min 15 min 5 minutes Yes
Slow NAP health, site health, citation health, reports 1 hour 4 hours 1 hour Yes
Static Plans, templates, help articles 24 hours 24 hours None No
On-demand Search, filters, export 0 0 None No

Mutation Patterns:

  • Optimistic updates: update cache immediately, rollback on error.
  • Invalidation: utils.invalidate() on success to trigger refetch.
  • Toast on success/error with action name.
  • Loading state: button spinner, form disabled, overlay blocker for heavy operations.

Prefetching:

  • Route prefetch: on hover of sidebar link, prefetch query for target route.
  • Dashboard prefetch: on login, prefetch all dashboard queries in background.
  • Modal prefetch: on hover of action button that opens modal, prefetch modal data.

1.4.5 Error Handling & Loading States#

Error Boundary:

  • Top-level error boundary catches all React errors.
  • Fallback UI: deep-ink background, paper-white text, error message, "Reload page" button, "Contact support" link.
  • Logged to Sentry (if configured) with user ID, route, error stack.

Query Error States:

  • Initial load error: Full-page error state. Icon (alert triangle), title ("Failed to load data"), description (error message from tRPC), "Retry" button (primary), "Contact support" link.
  • Background refetch error: Toast notification (error type), no UI change. Retry automatically on next interval.
  • Partial data error: Show available data + inline error banner at top of affected section. "Some data could not be refreshed. Retrying..." with spinner.

Loading States:

  • Skeleton: Pulsing paper-white to mist animation. Used for cards, tables, charts. Match layout of final content (same number of lines, same heights).
  • Spinner: Centered rotating spinner (Lucide Loader2 icon, forest-teal or ember-orange). Used for buttons, inline loading, small areas.
  • Progress bar: Linear progress bar (height 3px, ember-orange fill, mist track). Used for file uploads, long operations, onboarding steps.
  • Empty state: Icon + title + description + CTA. Used when data is loaded but empty. Example: "No reviews yet" + "Connect your GBP" button.

Retry Patterns:

  • Automatic: 2 retries with exponential backoff (1s, 2s, 4s).
  • Manual: "Retry" button on error state. Trigger refetch() on click.
  • Background: Silent retry on next polling interval.

1.5 Responsive Design#

1.5.1 Breakpoints#

Name Width Layout Changes
mobile < 640px Single column, stacked nav, full-width cards, hamburger menu, bottom sheet modals
tablet 640px – 1023px 2-column grids, collapsible sidebar (overlay), modals centered
desktop 1024px – 1279px Full sidebar visible, 3-column grids, modals centered
wide 1280px+ Max-width 1200px container, 4-column grids, full sidebar, slide-overs

1.5.2 Layout Rules#

  • Container: max-width: 1200px, margin: 0 auto, padding: 0 24px (desktop), 0 16px (mobile).
  • Sidebar: Always visible on desktop+. Collapsed to 64px icon-only on tablet. Hidden (hamburger) on mobile.
  • Main content: margin-left: 240px (sidebar width) on desktop+. Full width on mobile/tablet.
  • Card grid: 4 columns (desktop), 2 columns (tablet), 1 column (mobile). Gap: space-4 (16px).
  • Table: Horizontal scroll on mobile with sticky first column. Card-based list on mobile (optional).
  • Modal: Full-screen on mobile (slide-up from bottom). Centered on tablet+. Max-width: 100% on mobile, 560px on tablet, 720px on desktop.
  • Form: Single column on mobile/tablet. Two-column on desktop (with labels left-aligned).

1.5.3 Touch Targets#

  • Minimum touch target: 44px × 44px (Apple HIG) / 48px × 48px (Material).
  • Button height: minimum 40px (compact), 44px (standard).
  • Icon buttons: 40px × 40px container, icon 20px or 24px.
  • Form inputs: minimum height 44px.
  • Spacing between touch targets: minimum 8px.

1.6 Accessibility Requirements#

1.6.1 WCAG 2.1 AA Compliance#

  • Color contrast: All text meets 4.5:1 against background (AA). Large text (18px+ bold or 24px+) meets 3:1.
  • Focus indicators: All interactive elements have visible focus ring (shadow-focus, ember-orange, 3px offset). No focus trap except in modals.
  • Keyboard navigation: All functionality available via keyboard. Tab order follows visual order. Skip link provided for main content.
  • Screen reader: All icons have aria-label. Images have alt text. Forms have label associations. Live regions for toast notifications (aria-live="polite").
  • Reduced motion: Respect prefers-reduced-motion: reduce — disable animations, transitions, auto-playing content.

1.6.2 ARIA Patterns#

  • Modal: role="dialog", aria-modal="true", aria-labelledby (title ID), focus trap.
  • Toast: role="status", aria-live="polite", aria-atomic="true".
  • Sidebar: role="navigation", aria-label="Main navigation". Current page: aria-current="page".
  • Table: role="table", headers scope="col", row selection aria-selected.
  • Tabs: role="tablist", role="tab", role="tabpanel", aria-selected, aria-controls.
  • Accordion: role="region", aria-expanded, aria-controls.
  • Loading: role="status", aria-busy="true", aria-label="Loading".

1.6.3 Form Accessibility#

  • All inputs have associated <label> (not just placeholder).
  • Error messages linked via aria-describedby.
  • Required fields marked with aria-required="true" and visual asterisk (color error-red).
  • Grouped fields (radio, checkbox) use <fieldset> + <legend>.
  • Date inputs: use type="date" with aria-label for format hint.

1.7 Representative Widget: KPI Card (Depth Validation)#

This widget demonstrates the exact depth applied to every component in this specification.

1.7.1 KPI Card — Specification#

┌─────────────────────────────────────────┐
│ Total Views          ↑ 12.3%  [sparkline] │  ← Header row
│ 24,582                                  │  ← Value (heading-lg, 40px)
│ vs last month: 21,890                   │  ← Comparison (caption)
└─────────────────────────────────────────┘

1. Data to Show:

Field Type Format Source
label string "Total Views", "Total Clicks", etc. Static (configurable per card)
currentValue number Integer with comma separator report.getDashboardviews.total
previousValue number Integer with comma separator report.getDashboardviews.previous
changePercent number Signed percentage, 1 decimal Calculated: ((current - previous) / previous) × 100
changeDirection enum "up" | "down" Derived from changePercent sign
trendData number[] Array of 7 daily values report.getDashboardviews.trend
status enum "good" | "neutral" | "bad" Derived from thresholds (configurable)

2. Display Pattern:

  • Component type: MetricCard (composite: container + header + body + footer).
  • Layout: Vertical stack, flex column, space-3 (12px) gaps.
  • Container: card-white, radius-lg, shadow-subtle, padding 20px.
  • Header: Label (caption, 12px, text-tertiary, uppercase) left-aligned. Trend badge right-aligned.
  • Body: Value (heading-lg, 40px, weight 500, text-primary).
  • Footer: Comparison text (caption, 12px, text-tertiary). Sparkline (inline SVG, 60px wide, 30px tall).
  • Trend badge: If changeDirection === "up" → arrow-up icon + % value, color forest-teal. If down → error-red. If neutral (within ±1%) → slate.
  • Sparkline: Line chart (SVG path), stroke color matches trend badge, fill rgba 10% opacity, no axes, no labels, smooth curve.
  • Status border: If status === "bad", left border error-red 3px. If "good", no extra border.

3. Backend Endpoint:

  • Primary: report.getDashboard (tRPC query).
  • Zod input schema: z.object({ period: z.enum(["7d", "30d", "90d"]).default("30d") }).
  • Zod output schema: z.object({ views: z.object({ total: z.number(), previous: z.number(), trend: z.array(z.number()) }) }).
  • Auth: Requires CLIENT | EDITOR | VIEWER role. Returns 403 for unauthenticated.

4. Fetch Frequency:

  • Initial load: On dashboard mount.
  • Polling: Every 60 seconds (staleTime: 60s, refetchInterval: 60s).
  • Background refresh: On window focus (if staleTime exceeded).
  • Manual refresh: "Refresh All" button triggers utils.invalidate() for all dashboard queries.

5. Data Manipulations:

  • changePercent: ((current - previous) / previous * 100).toFixed(1).
  • changeDirection: changePercent > 0 ? "up" : changePercent < 0 ? "down" : "neutral".
  • trendData: Raw array from API. No manipulation (client renders as-is).
  • status: Configurable thresholds. Default: changePercent >= 5% → "good", changePercent <= -5% → "bad", else "neutral".
  • Formatting: currentValue.toLocaleString("en-IN") for Indian number format (comma separators).

6. Why Structured This Way:

  • 60s polling: Views/clicks are high-velocity metrics. Real-time not needed (no live dashboard), but stale data hurts trust. 60s balances freshness and server load.
  • Sparkline not full chart: Saves space in card grid. Full chart available in dedicated chart section below. Sparkline gives trend context without detail.
  • Indian number format: Target market is India. en-IN locale uses lakhs/crores (e.g., 1,00,000).
  • Color-coded trend: Immediate visual signal. Green = good, red = bad. No cognitive load.
  • Left border for bad status: Draws attention without overwhelming the card. Subtle but noticeable.
  • No bold weight: Brand constraint. 500 weight on 40px is visually heavy enough.

7. Interaction Flows:

  • Hover: Card lifts (translateY(-1px), shadow-sm), 200ms transition. No click action (read-only card).
  • Click: None (unless linked to detail view, configurable per card type).
  • Context menu: None.
  • Loading: Skeleton card (pulsing rectangle for label, larger rectangle for value, thin line for sparkline). Same dimensions as loaded card.
  • Error: Card shows inline error state. Icon (alert triangle, error-red), "Data unavailable", "Retry" button (ghost). Other cards unaffected.

8. Error States:

  • Empty: If currentValue === 0, show "0" as value, sparkline flat line at bottom, no trend badge. Not an error — just no data yet.
  • Loading: Skeleton (as described above).
  • Error: Inline error within card. Other cards show their data.
  • Partial: If trendData is missing but currentValue present, show value + "Trend unavailable" text instead of sparkline.

9. Role-Based Variations:

  • CLIENT: Full card, all data, no restrictions.
  • EDITOR: Same as CLIENT. No differences.
  • VIEWER: Same as CLIENT. No differences (read-only metric).
  • ADMIN: Not shown in client dashboard. Admin has own KPI dashboard with different metrics.

10. Threshold Configuration: Per-metric thresholds are configurable via dashboard.config (stored in localStorage or user preferences):

  • views.warningThreshold: 10% drop (default).
  • views.alertThreshold: 25% drop (default).
  • reviews.warningThreshold: 2-star average (default).
  • reviews.alertThreshold: 1-star average (default).
  • Thresholds affect status derivation and trigger toast notifications when crossed.

1.8 Icon System#

  • Library: Lucide React (lucide-react).
  • Stroke width: 1.5px (default from tokens).
  • Sizes: sm (16px), md (20px), lg (24px).
  • Color: Inherits from text color by default. Can be overridden per context.
  • Usage: Always paired with text label (except icon-only buttons which have aria-label).
  • Animated icons: Loader2 for spinners (rotate animation), Activity for live indicators (pulse).

1.9 Data Visualization (Charts)#

  • Library: Recharts (React).
  • Theme: All charts use design tokens for colors.
  • Line chart: Series colors from data tokens (series-1 through series-5). Grid lines grid (mist). Axis text caption (12px, text-tertiary). No bold.
  • Bar chart: Same colors. Rounded top corners (2px). Bar gap: 8px.
  • Pie chart: Colors from data tokens. Labels outside, connector lines. Legend below.
  • Area chart: Fill opacity 12%. Stroke width 2px.
  • Sparkline: No axes, no labels, no grid. Stroke width 2px. Smooth curve.
  • Tooltip: card-white background, shadow-sm, radius-lg, padding 12px. Header: body-sm weight 500. Value: body weight 400.
  • Legend: caption size, horizontal layout, clickable to toggle series.
  • Empty chart: "No data available" centered, text-tertiary, with icon.
  • Loading chart: Skeleton rectangle with pulsing animation, same aspect ratio.

1.10 Form Validation Patterns#

  • Library: Zod (shared with backend schemas).
  • Pattern: React Hook Form + Zod resolver.
  • Behavior: Validate on blur (not on every keystroke). Re-validate on submit.
  • Error display: Below field, caption (12px), error-red color, error icon left.
  • Success display: Check icon right of field, forest-teal color (optional, for complex forms).
  • Required marker: Asterisk after label, error-red color.
  • Disabled submit: If form invalid or submitting. Button shows spinner.
  • Field-level: Zod schema from backend (shared package) ensures client and server validation match.
  • Custom validators: Phone (Indian format), URL (with protocol), GSTIN (Indian tax ID), PIN code (6 digits).

1.11 File Upload Patterns#

  • Drag & drop: Visual drop zone with dashed border (mist), radius-lg, icon + text. Hover: border ember-orange, background rgba(236,101,43,0.04).
  • File types: Images (jpg, png, webp, gif), documents (pdf), max 10MB per file.
  • Validation: Type check, size check, dimension check (images). Error per file, dismissible.
  • Progress: Linear progress bar (3px, ember-orange), percentage text, file name.
  • Preview: Thumbnail for images, icon + name for documents. Remove button (X, top-right).
  • Upload: Direct to storage (S3/Cloudflare R2) via presigned URL from media.getUploadUrl (tRPC). Then send URL to backend.
  • Multiple files: Grid of thumbnails, max 10 files per upload.

1.12 Search & Filter Patterns#

  • Search input: Debounced 300ms. Icon left (Search), clear button right (X appears when text present).
  • Filter pills: radius-full, space-1 padding, removable (X icon). Stacked horizontally with wrap.
  • Filter sidebar: Slide-over from right (desktop), bottom sheet (mobile). Grouped by category.
  • Sort: Dropdown select or clickable table headers. Active sort: icon + highlight.
  • Pagination: Page numbers + prev/next. Page size selector (20/50/100).
  • URL sync: Filters, sort, page sync to URL query params for shareability and back-button support.
  • Empty search: "No results for 'query'" + suggestion to adjust filters.

1.13 Summary#

This section establishes the foundational design system and global patterns that all subsequent sections inherit. Every screen, widget, and interaction documented in Sections 2–15 must comply with:

  • Color rules: Ember orange for CTAs only. No bold weight. Deep ink for text.
  • Typography: Suisse Intl, 400/500 weights only, exact sizes from token system.
  • Spacing: 4px base unit, exact values from token system.
  • Components: Button, input, card, table, modal, toast patterns as documented.
  • Auth: Better Auth session, role-based guards, tRPC context.
  • Data fetching: React Query with categorized refresh rates.
  • Error handling: Skeleton, spinner, error boundary, retry patterns.
  • Responsive: 4 breakpoints, mobile-first, sidebar collapses.
  • Accessibility: WCAG 2.1 AA, focus rings, ARIA patterns, keyboard navigation.

The KPI Card example (Section 1.7) demonstrates the exact depth: 10-point specification for every widget, with exact data fields, endpoints, frequencies, manipulations, and UX rationale.


Next: Section 2 — Pre-Sales & Discovery Screens (/, /pricing, /contact)