Contour is a full-featured analytics dashboard — the kind of tool a SaaS product would build for its own customers. Overview, analytics, customers, transactions, reports, settings: six real views, built from scratch, with nothing but the browser platform. The brief was to prove I can build an application, not just a page.
What makes it work
- The chart is never the only way to read the data. Every chart ships with a real, always-present data table beside it, and keyboard-operable tooltips on the chart itself — nobody who can't easily read a line graph is locked out of the numbers.
- What you export is exactly what you were looking at. Reports reuse whatever metric, date range, and filters are already selected elsewhere in the app, instead of pulling from a separate dataset — the CSV export can never quietly disagree with the screen.
- A real three-way theme, not a toggle bolted on. Light, dark, and system — including live updates when the OS theme changes — built on one token system from the start, not a second stylesheet patched in afterward.
Design at a glance
- bg · #f5f6f4
- accent · #8a4a2b
- ink · #1b211c
- border · #e1e4df
- dark bg · #14181f
- dark accent · #c97a4a
Two complete token sets, not an inverted filter — the dark mode above is real, tuned color, the same way the light mode is.
- Customer drawer — pending capture
Built to last
- Fully responsive, tested 320px through 1440px+
- Zero dependencies to update or break — no framework, no build tooling
- Every status and trend reads clearly without relying on color alone
- A full keyboard focus trap in the customer detail drawer
For developers — the full technical breakdown
01. An accessible table beside every chart
- Problem
- A chart alone excludes anyone who can't easily read a line graph — low vision, colorblind, or just someone who wants the exact number.
- Choice
- Every chart ships with an always-present real data table beside it, plus keyboard-operable tooltips on the chart itself.
- Result
- The same information is available two ways, neither one a second-class citizen.
02. Reports read live app state, not a separate dataset
- Problem
- Dashboards commonly let an exported report quietly disagree with what's on screen.
- Choice
- Reports reuse whatever metric, range, and filters are already selected elsewhere in the app.
- Result
- A CSV export always matches exactly what was on screen at the moment it was exported.
03. A centralized store instead of a framework
- Problem
- KPIs, chart, tables, drawer, and settings all needed predictable, synchronized state without pulling in a framework.
- Choice
- A small centralized store with a subscribe/notify pattern — no virtual DOM.
- Result
- One-way data flow and zero framework dependency, in about the code a framework's boilerplate would have cost anyway.
Accessibility & performance
- Full ARIA states:
aria-sort,aria-current,aria-pressed,aria-expanded - A real focus trap in the customer detail drawer
- Status and trend information never conveyed by color alone
- Tested 320px through 1440px+
- Zero npm dependencies, zero build tooling, zero CDN assets
What I’d do differently
No automated test suite — quality was verified through a structured manual release audit rather than unit or integration tests. For an app this data- and interaction-heavy (sortable tables, filters, a focus-trapped drawer, a live chart), encoding some of that audit as automated tests would catch regressions faster next time.
Stack
- Vanilla JavaScript (ES modules)
- Hand-rolled SVG charts
- Centralized state store
- Static JSON via fetch()