Skip to content
Concept

Contour Dashboard

A full SaaS analytics dashboard — charts, customer tables, reports, and settings — built entirely in vanilla JavaScript, with zero dependencies.

Role
Design & development, solo
Year
2026
Stack
Vanilla JS, HTML, CSS
Status
Complete

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

Want a tool this considered built for your own data?

See it live Start a project
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()