# Flight Radar — Design System **Version 1.0 · February 2026** This document is the authoritative reference for the visual design and UX of the Flight Radar web application. It covers the full design system: principles, tokens, components, and page-level specifications — ready for direct implementation in Tailwind CSS v4 + React. --- ## Table of Contents 1. [Current State Audit](#1-current-state-audit) 2. [Design Principles](#2-design-principles) 3. [Color System](#3-color-system) 4. [Typography](#4-typography) 5. [Spacing & Layout Grid](#5-spacing--layout-grid) 6. [Elevation & Shadow](#6-elevation--shadow) 7. [Component Library](#7-component-library) 8. [Page Specifications](#8-page-specifications) 9. [Motion & Animation](#9-motion--animation) 10. [Icons](#10-icons) 11. [Implementation Checklist](#11-implementation-checklist) --- ## 1. Current State Audit ### What's broken today | Area | Problem | Impact | |------|---------|--------| | **Navigation** | Nav links butted together with no gaps: renders as `DashboardScansAirportsLogs` | Unreadable, looks broken | | **Dashboard stats** | Five identical white boxes with no accent, icon, or trend | Zero visual hierarchy | | **Scan list items** | Status badge glued to destination: `BDS → FMM,DUScompleted` | Parsing required by user | | **Form** | Flat input fields, no grouping, no visual section breaks | Feels like a raw HTML form | | **Mode toggle** | Full-width button pair looks like a toggle switch gone wrong | Confusing affordance | | **Airports page** | Empty white canvas with an input box — no state, no branding | Dead end feeling | | **Logs page** | Wall of monospaced text with no log-level differentiation | No scanability | | **Mobile** | Nav links overflow and collapse poorly | Unusable on small screens | | **Loading states** | Plain text "Loading…" or invisible spinner | No feedback | | **Empty states** | Plain text only, no illustration or call to action | Abandoned feeling | | **Colors** | Two blues + basic status colours, no system | Incoherent palette | | **Typography** | Browser default font, no size scale | Low credibility | | **Branding** | ✈️ emoji as logo | Not scalable, not professional | ### What's good and must be kept - Tailwind CSS v4 already installed — just needs design tokens - React Router working, page structure sound - Component boundaries (AirportSearch, Layout, pages) are clean - Status badge color logic is correct - Auto-refresh on running scans is a great UX feature - Expandable flight rows in the route table are the right pattern --- ## 2. Design Principles ### 1. Data first, chrome second Flight data is complex (many numbers, dates, airports). The UI must recede and let data breathe. Avoid decorative elements that compete with content. ### 2. Material Design 3 — adapted, not copied Use MD3's color system (tonal palettes, surface roles), elevation model (tonal elevation, not drop shadows), and component shapes (rounded corners). Ditch components that don't fit a desktop tool (FABs, bottom nav for desktop). ### 3. Mobile-first, desktop-optimised Start every layout from 320 px. Desktop adds a sidebar and wider data tables — it doesn't redesign the page. ### 4. Instant feedback everywhere Every async action shows a skeleton, spinner, or progress indicator. Every destructive or slow action is confirmed. Success and error are always communicated via toast. ### 5. One primary action per view Each page has exactly one primary CTA (blue filled button). Everything else is secondary or ghost. --- ## 3. Color System ### Brand palette (MD3 tonal palette — primary hue: blue 220°) ``` Primary #1A73E8 (Google Blue — familiar, trustworthy for a data tool) On Primary #FFFFFF Primary Cont. #D2E3FC (primary container — used for active nav, chips) On Primary Cont.#003E8C Secondary #0F9D58 (green — price / success / confirmed flights) On Secondary #FFFFFF Secondary Cont. #C8E6C9 Tertiary #F4B400 (amber — warnings, pending states) On Tertiary #3B2A00 Tertiary Cont. #FFF0C0 Error #D93025 (red — failed, error) Error Cont. #FDECEA Surface #F8F9FA (page background — very light grey) Surface 1 #FFFFFF (card background — level 1) Surface 2 #F1F3F4 (table row alternate / subtle separators) Surface Variant #E8EAED (input backgrounds, chip backgrounds) On Surface #202124 (primary text) On Surface Var. #5F6368 (secondary text, labels, metadata) Outline #DADCE0 (borders, dividers) Outline Var. #F1F3F4 (very subtle separators) ``` ### Semantic status colours | Status | Background | Text | Border | Usage | |--------|-----------|------|--------|-------| | `completed` | `#E6F4EA` | `#137333` | `#A8D5B5` | Scan finished, route confirmed | | `running` | `#E8F0FE` | `#1557B0` | `#A8C7FA` | Scan in progress | | `pending` | `#FEF7E0` | `#7A5200` | `#F9D659` | Queued, not started | | `failed` | `#FDECEA` | `#A50E0E` | `#F5C6C6` | Error state | ### Dark mode (implement via CSS variables) ```css /* light (default) */ :root { --color-bg: #F8F9FA; --color-surface: #FFFFFF; --color-surface-2: #F1F3F4; --color-on-surface: #202124; --color-on-surface-var: #5F6368; --color-outline: #DADCE0; --color-primary: #1A73E8; --color-secondary: #0F9D58; } /* dark */ @media (prefers-color-scheme: dark) { :root { --color-bg: #1C1B1F; --color-surface: #2C2B30; --color-surface-2: #36343B; --color-on-surface: #E6E1E5; --color-on-surface-var: #938F99; --color-outline: #49454F; --color-primary: #A8C7FA; --color-secondary: #81C995; } } ``` --- ## 4. Typography ### Font stack ```css font-family: 'Google Sans', 'Roboto', system-ui, -apple-system, sans-serif; font-family-mono: 'Roboto Mono', 'JetBrains Mono', monospace; /* logs, IATA codes */ ``` > Load from Google Fonts: `Google Sans` (300, 400, 500) + `Roboto Mono` (400) ### Type scale (MD3 aligned) | Token | Size | Weight | Line height | Usage | |-------|------|--------|-------------|-------| | `display-sm` | 36px / 2.25rem | 400 | 44px | Page hero (empty states) | | `headline-lg` | 32px / 2rem | 400 | 40px | — | | `headline-md` | 28px / 1.75rem | 400 | 36px | Page title | | `headline-sm` | 24px / 1.5rem | 400 | 32px | Section title | | `title-lg` | 22px / 1.375rem | 500 | 28px | Card title | | `title-md` | 16px / 1rem | 500 | 24px | List item title, form label | | `title-sm` | 14px / 0.875rem | 500 | 20px | Table header, tab label | | `body-lg` | 16px / 1rem | 400 | 24px | Body copy, descriptions | | `body-md` | 14px / 0.875rem | 400 | 20px | Secondary body, list metadata | | `body-sm` | 12px / 0.75rem | 400 | 16px | Captions, helper text | | `label-lg` | 14px / 0.875rem | 500 | 20px | Button labels | | `label-md` | 12px / 0.75rem | 500 | 16px | Badge labels, chip labels | | `label-sm` | 11px / 0.6875rem | 500 | 16px | Overlines, micro labels | | `mono-md` | 13px / 0.8125rem | 400 | 20px | IATA codes, log messages | --- ## 5. Spacing & Layout Grid ### Spacing scale (4px base) ``` 1 → 4px (tight gaps: icon-to-text, badge padding) 2 → 8px (small gaps: form field helper text) 3 → 12px (medium-small: list item inner padding) 4 → 16px (base unit: card inner padding mobile) 5 → 20px (—) 6 → 24px (card inner padding desktop, section gaps) 8 → 32px (between major sections) 10 → 40px (—) 12 → 48px (page top padding) 16 → 64px (large empty state illustrations) ``` ### Layout grid **Mobile (< 600px)** - 1 column, 16px margins, 16px gutters - Full-width cards - Bottom navigation bar (56px) **Tablet (600–1024px)** - 8 columns, 24px margins, 24px gutters - Side rail navigation (72px collapsed) - Cards: 2-column grid for stats **Desktop (> 1024px)** - 12 columns, 24px margins, 24px gutters - Fixed sidebar navigation (256px) - Max content width: 1280px - Cards: 3–5 column grid for stats ### Sidebar layout (desktop) ``` ┌──────────┬────────────────────────────────┐ │ │ Top bar (64px, search + user) │ │ Sidebar ├────────────────────────────────┤ │ (256px) │ │ │ │ Main content area │ │ Logo │ max-w: 1024px, centered │ │ Nav │ px: 24px │ │ items │ │ │ │ │ │ ────── │ │ │ Logs │ │ └──────────┴────────────────────────────────┘ ``` --- ## 6. Elevation & Shadow MD3 uses **tonal elevation** (surface tint) as primary depth cue, with subtle shadows for resting state only. | Level | Shadow | Tint overlay | Usage | |-------|--------|-------------|-------| | 0 | none | 0% | Page background | | 1 | `0 1px 2px rgba(0,0,0,.08)` | 5% primary | Cards (resting) | | 2 | `0 2px 6px rgba(0,0,0,.10)` | 8% primary | Dropdowns, popovers | | 3 | `0 4px 12px rgba(0,0,0,.12)` | 11% primary | Modals, dialogs | | 4 | `0 6px 20px rgba(0,0,0,.14)` | 12% primary | Navigation drawer | --- ## 7. Component Library ### 7.1 Navigation #### Sidebar (desktop ≥ 1024px) ``` Width: 256px (expanded) / 72px (collapsed — future) Background: Surface (white) Border-right: 1px solid Outline (#DADCE0) Logo area (64px tall): ✈ icon (24px, Primary blue) + "Flight Radar" (title-lg, On Surface) Nav item (48px tall, full width): - Inactive: transparent bg, On Surface Var text, 16px side padding - Active: Primary Container bg (#D2E3FC), On Primary Container text, bold icon, rounded 28px pill shape (full bleed) - Hover: Surface 2 bg, On Surface text - Icon: 20px, left-aligned - Label: body-md 500 weight, 12px left of icon Section divider: Between main items and Logs: 1px divider + "Developer" label (label-sm, muted) Items (in order): 🏠 Dashboard → / 🔍 Scans → /scans ✈ Airports → /airports ── (divider) 📋 Logs → /logs ``` #### Top app bar (desktop) ``` Height: 64px Background: Surface (white) Border-bottom: 1px solid Outline Shadow: Level 1 on scroll Left: (sidebar occupies) Center: Page title (headline-sm, On Surface) — OR breadcrumb Right: + New Scan button (filled, primary) ``` #### Bottom navigation (mobile < 600px) ``` Height: 80px (includes safe area) Background: Surface Border-top: 1px solid Outline Shadow: Level 2 (upward) 4 items: Dashboard | Scans | Airports | Logs Each: icon (24px) + label (label-md) stacked Active: Primary colour icon + Primary Container indicator pill behind icon (56×32px) Inactive: On Surface Var colour ``` --- ### 7.2 Buttons #### Filled (primary action — one per page) ``` Background: Primary (#1A73E8) Text: On Primary (#FFFFFF) Height: 40px Padding: 0 24px Border-radius: 20px (fully rounded — MD3 style) Font: label-lg (14px/500) Shadow: Level 1 Hover: darken 8% + Level 2 shadow Pressed: darken 12%, Level 1 Disabled: 38% opacity Icons: optional leading icon at 18px ``` #### Outlined (secondary action) ``` Border: 1px solid Outline (#DADCE0) Text: Primary (#1A73E8) Background: transparent Same sizing/radius as filled Hover: Primary Container bg (#D2E3FC) ``` #### Text (tertiary / destructive link) ``` Text: Primary or Error No border, no background Hover: surface tint Padding: 0 12px ``` #### Icon button ``` Size: 40×40px Shape: circle Background: transparent → Surface 2 on hover Icon: 20px, On Surface Var → On Surface on hover ``` --- ### 7.3 Status Chip / Badge ``` Height: 24px Padding: 0 10px Border-radius: 12px (pill) Font: label-md (12px/500) Border: 1px solid (matching border column from §3) ┌──────────────────────┐ │ ● completed │ green bg, green text, green dot │ ↻ running │ blue bg, blue text, spinning dot (CSS animation) │ ⧖ pending │ amber bg, amber text, static dot │ ✕ failed │ red bg, red text, X icon └──────────────────────┘ ``` Implementation: The colored dot/icon is a 6px circle or SVG icon on the left at 8px margin-right. --- ### 7.4 Stat Card Used on Dashboard (5 cards) and Scan Details (3 cards). ``` ┌────────────────────────┐ │ ┌──┐ │ │ │ 🔍│ Total Scans │ ← icon in tinted circle (40px), label (body-sm, muted) │ └──┘ │ │ │ │ 54 │ ← number (display-sm or headline-md) │ │ │ +3 today │ ← optional trend (body-sm, secondary/error) └────────────────────────┘ Padding: 24px Border-radius: 16px (MD3 large shape) Background: Surface (white) Shadow: Level 1 Width: fluid (grid cell) Icon circle: 40×40px, border-radius 20px Total: #E8F0FE bg, Primary icon Pending: #FEF7E0 bg, Tertiary icon Running: #E8F0FE bg, Primary icon (animated pulse) Completed: #E6F4EA bg, Secondary icon Failed: #FDECEA bg, Error icon ``` --- ### 7.5 List / Scan Card Replaces the current flat `` row in the dashboard. ``` ┌──────────────────────────────────────────────────────────┐ │ BDS → FMM, DUS ● completed 25.2.2026 │ │ 26 Feb 2026 – 27 May 2026 · 1 adult · Economy │ │ │ │ 182 routes · 50 flights found → │ └──────────────────────────────────────────────────────────┘ Padding: 16px 20px Border-radius: 12px (on hover, or always) Background: Surface Hover: Surface 2 bg + Level 2 shadow (lift effect) Divider: 1px inside, OR 8px gap between cards (prefer cards) Cursor: pointer Route arrow (→): On Surface Var, shifts right on hover (transform) Status chip: right-aligned in first row, always visible Date metadata: body-sm, On Surface Var Stats line: body-sm, On Surface Var, shown only when > 0 ``` --- ### 7.6 Text Input & Form Field MD3 "Outlined" input variant. ``` Height: 56px Border: 1px solid Outline Border-radius: 4px (corners) — MD3 uses 4px for inputs (not pills) Background: transparent (on Surface bg it appears inset) Padding: 16px Labels: floating label (HTML standard — use