# 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