Tidemill Design System
00 CANONICAL SYSTEM · MAY 2026

Tidemill Design System.

A warm orange-based palette designed for financial SaaS dashboards. One source of truth — mirrored across the frontend, the Plotly report style, the documentation theme, and the logo set.

PRIMARY · AMBER 500 INK · STONE 900 SURFACE · #FFFFFF 9-SERIES COLORWAY
01 Brand

Three swept blades. One amber hub.

The mark is a three-blade turbine — tide and mill, evoked in a single rotation. Orange-to-amber gradients on the upper and right blades, a single amber-to-amber blade for the lower-left accent, all spinning around a solid #EA580C hub.

Logo lockups
logo color
Colorlogo-color.svg
logo bw
Blacklogo-bw.svg
logo white
White (on dark)logo-white.svg
Icon set
icon
icon-color
icon
icon-bw
icon
icon-white
favicon
favicon
Brand swatches
NameHexUsage
Amber 500 PRIMARY#F59E0BPrimary brand · UI primary · colorway lead
Orange 600#EA580CHub fill · favicon · past-due status
Orange 500#F97316Logo blade gradient start · wordmark
Orange 700#C2410CLogo blade gradient end
Amber 600#D97706Logo accent blade gradient end
02 Color

Single source of truth, four scales.

Implemented in frontend/src/lib/colors.ts, frontend/src/index.css, and tidemill/reports/_style.py. Every chart, every report, every component reads from the same constants.

Semantic — charts & metrics

9 colors mapped to specific roles
RoleHexMapped to
Positive / Growth#16A34Anew · active · converted · GRR
Expansion#2563EBexpansion · NRR
Contraction#EAB308contraction
Churn / Negative#DC2626churn · canceled · expired · logo churn
Special#8B5CF6reactivation · ARPU
Brand accent#F59E0Btrialing · revenue churn
Warning#EA580Cpast due
Neutral#78716Cstarting MRR · pending · grey
Dark#1C1917ending MRR

Multi-series colorway

Default cycle for n-series charts, in order
1#F59E0Bamber
2#2563EBblue
3#16A34Agreen
4#8B5CF6violet
5#DC2626red
6#0891B2cyan
7#DB2777pink
8#84CC16lime
9#78716Cstone

Sequential scale — heatmaps

Orange ramp used by Plotly colorscale_sequential
0.00#FFF7EDorange-50
0.25#FED7AAorange-200
0.50#FB923Corange-400
0.75#EA580Corange-600
1.00#431407orange-950

Cohort retention buckets

Discrete · green→red
≥ 90% #DCFCE7 ≥ 70% #BBF7D0 ≥ 50% #FEF08A ≥ 30% #FED7AA < 30% #FECACA

Neutral tones — stone

Warm grays for text, grids, borders
HexTailwindUsage
#E7E5E4stone-200Grid lines
#D6D3D1stone-300Axis lines
#78716Cstone-500Secondary text · neutral data
#44403Cstone-700Body text · hover labels
#1C1917stone-900Titles · headings

UI theme — CSS custom properties

frontend/src/index.css
PropertyValuePurpose
--color-primary#F59E0BButtons · links · focus rings
--color-primary-foreground#FFFFFFText on primary
--color-accent#FFFBEBHover / highlight backgrounds
--color-accent-foreground#1C1917Text on accent
--color-destructive#DC2626Danger actions
--color-ring#F59E0BFocus outlines
--color-border#E5E5E5Default borders
--color-muted#F5F5F5Page background
03 Type

Inter, set tight.

A single typeface — Inter — for headlines, body, and UI. Numerals are tabular by default; titles use stone-900 with negative letter-spacing; secondary text settles into stone-700. Monospace appears only where developers need to read literals: code, endpoints, eyebrows, metric values.

Display
64 / 1.1 / -0.025em
Subscription metrics
H1
48 / 1.1 / -0.025em
Tidemill design system
H2 — Section
36 / 1.1 / -0.025em
Every metric, with its formula.
H3 — Subsection
24 / 1.25 / -0.01em
Webhook or direct database.
Lead
18 / 1.45
Each metric lives in a self-contained Python module with its formula, SQL, edge cases, and API in one place.
Body
16 / 1.5
Compiled SQL travels through a typed query algebra — never string concatenation.
Small
14 / 1.5
PostgreSQL + Redpanda + FastAPI + a React dashboard, behind Caddy with auto-TLS.
Meta — mono
12 / 1.5
tidemill.metrics.mrr.waterfall()
Eyebrow — mono
11 / 1.4 · 0.12em
02 — METRICS
04 Space & radius

4-px base, three radii.

Components step in 4-pixel increments up to 32, then jump to 48, 72, and 112 for section-level rhythm. Radii are inherited from the frontend: --radius-sm · --radius-md · --radius-lg.

Spacing scale
4 · sp-1
8 · sp-2
12 · sp-3
16 · sp-4
24 · sp-5
32 · sp-6
48 · sp-8
72 · sp-10
112 · sp-12
Radius
4 · sm · 0.25rem
6 · md · 0.375rem
8 · lg · 0.5rem
05 Chart patterns

Semantic colors carry the story.

Charts always reach for the semantic palette first — green for growth, red for churn, blue for expansion, amber for the brand series. The multi-series colorway is only used when a chart has no inherent meaning to its categories.

MRR waterfall · semantic mapping
MRR Waterfall · Nov 2025 → Apr 2026
LIVE DEMO DATA
Starting New Expansion Reactivation Contraction Churn
tidemill.metrics.mrr.waterfall() GET /api/metrics/mrr/waterfall
Cohort retention · heatmap
06 Components

A small kit, composed from tokens.

Every component below is rendered from the canonical CSS variables. Adjust a token in tokens.css and the whole page updates.

Buttons
Status badges · semantic
Active Trialing Past due Canceled Reactivated New Expansion Contraction
Stat row
MRR · APR 2026$147.6k▲ 4.2% MoM
Net Revenue Retention108%▲ 1.1 pp
Logo Churn2.1%▼ 0.4 pp
Subscribers3,284▲ 142
Form controls
Cards

Transparent by construction

Every metric is a Python class. Read the formula, read the SQL.

Pluggable connectors

Stripe webhooks. QuickBooks. Same-database mode. Add one in < 200 LOC.

Yours to run

Docker Compose on a €4/mo Hetzner box, or a k3s HA cluster.

Code block
Pythontidemill/metrics/mrr/metric.py
# Active recurring revenue at time t.
@register
class MRR(Metric):
    async def query(self, spec: QuerySpec) -> Select:
        c = self.cube
        return spec.fragment(
            select=[c.sub.mrr_base_cents.sum().label("mrr")],
            where=[c.sub.is_active_at(spec.at)],
        )
Section header anatomy
02 METRICS

Every metric, with its formula.

Six metric families ship today. Each one is a separate module with its own tables, event handlers, and API routes — adding a new metric requires zero changes to existing code.