Design System · v2 · Dark-first

Consistent the
first time.

A dark, sharp, OKLCH token system for data-dense dashboards. Link two files, drive the theme from three variables, and every screen ships consistent. No hardcoded values, no drift.

Tickets · Season 2026 Live 2m ago
Revenue
$284K8.2%
Tickets
8,14212%
Fill
94%0.0%
Supporters88%
Premium34%
Drive the system. Three variables re-skin every surface, border, and accent on this page. Blurple (hue 270 to 285) is out of range by design.
Accent hue195
Secondary hue320
Contrast1.00
00

Start here

Two stylesheets carry the whole system: fonts, palette, type scale, spacing, and every component on this page. Link them in the head, in this order, then use the tokens.

<link rel="stylesheet" href="https://design.seantippen.com/tokens.css">
<link rel="stylesheet" href="https://design.seantippen.com/components.css">

Three rules

01
Tokens, never literals
Use var(--token) in CSS and tok('--token') in Chart.js. Never paste a hex, rgba, or oklch value. The one exception is Chart.js dataset colors, which the Canvas API cannot resolve from CSS.
02
OKLCH, sharp by default
Custom colors are OKLCH for perceptual uniformity. Corners are 0 by default; rounded is reserved for pills, toggles, and circular controls. No gradient page or section backgrounds, no gradient text.
03
Cyan leads, pink supports
One accent per layout. Cyan is the signature; neon pink is a state and secondary color, used sparingly. Never blurple. Amber is a warning state, never decoration.

Lock dark mode on brand-critical pages

Stops a reader's dark-mode browser from re-mapping the palette and making the brand look wrong. Add a theme-color meta matching --color-bg for the address bar.

<meta name="color-scheme" content="dark">
<meta name="darkreader-lock">
01

Color

OKLCH for all custom colors, captioned by the job each one does. Hex only for Chart.js data series (Canvas API). Click any swatch to copy its value. Drive the hue with the generator above and watch every swatch re-derive.

Brand accents

--color-accent
Solid fill, active state, the one rationed color
--color-accent-hover
Hover on accent surfaces
--color-accent-border
Interactive border, focus ring
--color-secondary
Neon pink. Second series or contrast moment
--accent-muted
Selected background tint
--accent-subtle
Faint accent wash

Surfaces, five-step elevation

Depth comes from lightness, not shadow. App background through floating overlay.

App bg
--color-bg
Surface
--color-surface
Hover
--color-surface-hover
Raised
--color-surface-raised
Overlay
--color-surface-overlay

Text hierarchy

Four steps, never pure white. Chrome and labels recede to the lower steps so data leads.

--color-text
High-contrast body and headings
--color-text-secondary
Supporting copy
--color-text-muted
Labels, captions, chrome
--color-text-disabled
Disabled, ghost

Status

Completed Pending Overdue In review Synced Delayed Failed Queued

Green success, amber warning, red danger, blue info. Amber is a warning state only, never a decorative accent.

Data series (Chart.js palette)

Eight hex colors at the same perceived brightness so no series dominates. Hex because the Canvas API cannot read CSS custom properties at paint time.

green
blue
red
amber
purple
cyan
yellow
orange

Contrast, computed live

Recomputed from the active tokens. Drive the generator or toggle the theme and the ratios recalculate.

PairRatioGrade
Body text on canvas.
Secondary on canvas.
Muted on canvas.
Accent on canvas.
02

Typography

A committed three-face system: Big Shoulders Display for the one hero header, Inter for everything else, JetBrains Mono for numbers and code. Inter is rationed body in a three-font system, not the only face. Fluid sizing via clamp().

The three faces

Big Shoulders Display · --font-display · hero only
Consistent the first time
Inter · --font-sans · body and UI
The quick brown fox jumps over the lazy dog
JetBrains Mono · --font-mono · numbers and code
$2,847,392.00

Hierarchy specimen

Display 4XL
Section title 2XL
Subsection XL
Lead paragraph LG
Body base at the measure of 65ch. Generous line-height keeps long reading calm; the measure token stops lines from running too wide to scan.
Small SM, supporting copy and dense tables.
XS, captions and metadata.

Weight scale

Light, recessive labels300
Regular body400
Medium, active state500
Semibold, card titles600
Bold, subheads700
Extra bold, display800
03

Spacing & motion

Fluid spacing scales between viewport poles via clamp(). Motion is token-timed, ease-out by default, spring for delight, always behind prefers-reduced-motion.

Spacing scale

--space-xs
clamp(0.25rem, 0.2rem + 0.2vw, 0.5rem)
--space-sm
clamp(0.5rem, 0.4rem + 0.4vw, 0.875rem)
--space-md
clamp(1rem, 0.875rem + 0.5vw, 1.5rem)
--space-lg
clamp(1.5rem, 1.25rem + 1vw, 2.5rem)
--space-xl
clamp(2rem, 1.5rem + 2vw, 4rem)

Motion tokens

TokenValueUse
--duration-fast150msHover, focus, small state changes
--duration-normal250msDrawers, view transitions
--ease-outdecelerateDefault for functional motion
--ease-springovershootDelight only (toast, palette)
04

Components

Everything below is rendered from the real components.css classes, the same sheet your dashboard links. Flip any demo to Code and copy the exact markup.

Buttons

Input & chips

Active ×All regionsLast 30 days

Badges & toasts

CompletedPendingOverdueIn review

Skeleton loading

05

Data display

KPIs, charts, and tables. No card wrappers; data lives directly on the surface. Numbers are monospace with tabular figures so columns lock.

KPI markers

Total revenue
$2.4M
Tickets sold
18,472
Avg order
$128
Sell-through
94.2%

Bar chart

Chart.js 4.4.7, colors via tok(), global defaults wired to tokens (square corners, mono ticks).

Data table

RegionRevenueGrowthStatus
Northeast$842,500+12.3%Growing
Southeast$621,300+8.1%Growing
Midwest$534,700+2.4%Stable
West Coast$412,800-3.7%Declining
06

Dashboards

Composite patterns for data-dense surfaces. Every block is a stock component, composed the way a real dashboard composes them. No bespoke CSS.

Composed dashboard

Tickets · Season 2026
Revenue
$284,6108.2%
vs last week
Tickets
8,14212%
sold this month
STH base
2,3574%
season holders
Fill
94.2%0.0%
last match
RepSoldRevenue
Avery1,284$58,210
Mateo1,102$49,870
Priya986$44,120
Total3,372$152,200

Primitive set

Delta chips, progress, pills, toolbar, stale timestamps, empty and error states, drawer. All ship in components.css.

12.3%3.7%0.0% LiveSyncedDelayedFailed 2m ago14m ago2h ago
Supporters88%
Premium34%
Active ×All regions
Updated 2m ago
No matches yet
Once tickets sell for this event, activity appears here.
Could not load data
Vivenu API returned a 502. This usually clears within a minute.
07

Motion & depth

Felt, not seen. One orchestrated page-load reveal, native scroll-driven section reveals, View Transitions on theme and accent swaps, and a token-colored cursor spotlight. Everything respects prefers-reduced-motion.

Cursor spotlight

A radial accent glow tracks the pointer. JS writes two CSS variables; the compositor draws the gradient.

Hover me
color-mix accent glow, on-palette in any theme.
And me
Gated behind reduced-motion.
Lift, never scale
Depth from shadow and light, not transform-scale.

Frosted glass & shadow elevation

Glass is for overlays only (modals, dropdowns, command palette), never flat cards. Shadows convey hierarchy, not decoration.

Frosted overlay
backdrop-filter blur over a busy backdrop. Reserved for floating surfaces.
Shadow SM
Shadow MD
Shadow LG
08

Triumph overrides

Quick Triumph builds can reuse this system by swapping the accent hue to the crest colors. The full Triumph dashboard system is separate, at design.greenvilletriumph.club.

Personal default
Cyan
--accent-h: 195
Neon pink
--secondary-h: 320
Triumph crest
Green
oklch(0.72 0.19 155)
Blue
oklch(0.69 0.18 255)
09

Anti-patterns

What not to do, and what to do instead. These are the lines between an elite, intentional interface and generic AI output.

Don't
Scroll-with-content gradient backgrounds that shift how content reads.
Do
Flat var(--color-bg), or one position:fixed palette-cohesive field that never moves.
Don't
Blue-purple gradient hero (blurple, hue 270 to 285). The single most recognizable AI tell.
Do
One rationed cyan accent; neon pink oklch(0.70 0.25 320) as a state color.
Don't
Puffy uniform rounded cards as the whole layout.
Do
Sharp corners by default; rounded only for pills, toggles, and circular controls.
Don't
transform: scale() on hover; it is janky and displaces layout.
Do
translateY(-2px) lift plus a deeper shadow, or a token-colored spotlight.
Don't
Gradient text on headers; it looks cheap and breaks on some backgrounds.
Do
Plain var(--color-text) or var(--color-accent) with a weight bump.
Don't
Catalog every effect at once; it signals "I can do effects," not taste.
Do
One orchestrated load reveal and one canonical hover. Restraint reads as elite.
10

Ship checklist

Run this before any dashboard goes live. If every line is true, it ships consistent.

11

Token reference

The generator tier sits on top: change four variables, re-skin everything. The full set lives in tokens.css.

/* Generator tier, drives the whole system */
--accent-h: 178      --secondary-h: 350
--base-h: 252        --contrast: 1

/* Accent and surface (derived) */
--color-accent   --color-accent-hover   --color-accent-border
--color-secondary
--color-bg   --color-surface   --color-surface-hover
--color-surface-raised   --color-surface-overlay

/* Text hierarchy */
--color-text   --color-text-secondary
--color-text-muted   --color-text-disabled
--text-alpha-90 to --text-alpha-15

/* Status */
--color-success   --color-warning   --color-danger   --color-info

/* Chart data series (hex, for Chart.js) */
--green --blue --red --amber --purple --cyan --yellow --orange

/* Type */
--font-sans   --font-mono   --font-display
--text-xs to --text-4xl      /* fluid clamp scale */

/* Spacing / motion / radius */
--space-xs to --space-xl      --space-section
--duration-fast   --ease-out   --ease-spring
--radius: 0      --radius-full: 9999px
Tokens
.
tokens.css
.
components.css
.
Components
.
Surface steps
5

Figures above are computed at runtime from the live sheets, not hardcoded.