PEWO-72 · Dashboard

Dashboard design system — one pager (v0.1)

Tokens & widget patterns used across the Dashboard concepts gallery. Extracted from real EW Office captures and reused in styles.css + concepts 1–5. Kept deliberately small — extend only when a new concept needs a pattern that isn't here yet.

1 Foundations — color

Navy
--navy #00004B
Primary
--primary #0067E6
Primary dark
--primary-dark #0045A0
Success
--success #11884E
Warning
--warning #FFB24C
Error
--error #E61E2B
Background
--bg #F5F5FA
Surface
--surface #FFFFFF
Neutral / offline
#A6A6BF

Text uses navy at reduced opacity for hierarchy: navy-87 primary text · navy-60 secondary/captions · navy-26 disabled.

2 Typography & base metrics

AaPage title — 28px / 800 / -0.5px tracking
AaWidget title — Typography/H6: Gilroy 600, 18px / 24px line-height, color rgba(0,0,75,.87)
AaBody / buttons — Inter 14px / 600, line-height 20px
AaSubtitle / caption — Inter 13px / navy-60

Typeface: Gilroy 600 for headings (H6 widget titles) · Inter (400/500/600/700) for everything else — no serif, no other sans. Radii: card 8px · button/input 4px · pill 999px. Border: 1px solid rgba(0,0,75,0.16). Shadow (card): 0 1px 2px rgba(0,0,75,.04) — barely there.

3 Layout

ElementSpec
Sidebar256px fixed (64px collapsed)
Topbar64px
Widget gridxs=12 · sm=6 · md=6 · lg=4 — up to 3 widgets per row ≥1536px
Widget row height430px (down from 490px — majority of customers run 1920×1080; keeps the row + nav visible without scrolling)

4 Spacing

A 4px-base scale used for gaps/margins between widget elements (tile grids, chip↔title, card sections). Previously undefined — added as tokens so horizontal and vertical gaps stay in sync instead of one-off pixel values.

TokenValueTypical use
--space-14pxicon ↔ label micro-gaps
--space-28pxtight stacks
--space-2-510pxtile grid gap (horizontal & vertical) — e.g. Fleet status KPI grid, Available-balance status cards
--space-312pxicon ↔ text inside a tile
--space-416pxsection-to-section spacing inside a card
--space-520pxcard outer padding

Every gap and margin uses a token — never a raw pixel value. Off-scale numbers (14px, 9px, 5px…) are how a layout quietly drifts: they look fine in isolation but stop the widgets lining up with each other. Component-internal metrics that the spec pins exactly — button padding 5px 8px, chip padding 3px 4px, segmented-control padding — are the one exception, because they define the component rather than the space around it.

Proximity beats symmetry. A label belongs to the thing it labels, so it sits closer to it than to whatever comes next: --space-2 from the big figure down to "Total balance", --space-1 from the progress bar down to its Available/Spent legend. Even section spacing everywhere reads as an undifferentiated list.

Bottom-align the last block. Give the closing element of a card (.ew-tiles-grid, .ew-tr-foot, a pinned CTA) margin-top: auto so leftover height collects in the middle, not under the last row. Cards then share a common baseline across the grid — 17px from the card's bottom edge.

Defined in concept-5/widgets.css (custom widgets are extracted there from the raw MUI capture, class-based instead of inline styles, so future adjustments touch one small stylesheet). Not yet promoted into the shared styles.css — worth doing once a second concept needs the same scale.

5 Widget card anatomy

Every widget (Fleet status, Tax refund, Available balance…) follows the same shape:

TitleGilroy 600, 18px/24px, rgba(0,0,75,.87)
Subtitle13px / navy-60 — optional, one line
ContentKPIs / chart / illustration / list
Primary CTApinned to bottom (margin-top:auto)

KPI tile pattern (2×2 grid inside a widget): icon bubble color carries status meaning, not decoration.

84Driving
6Idling
31Stopped
84Offline

Vehicle status colors are shared with the live map — Driving #2AABEB · Idling #F59E0B · Stopped #8A8AA3 · Offline #B4B4C8. The glyphs (navigation arrow / pause / square / no-signal) are the same 24px paths the map pins use, so a vehicle looks identical in the tile, the fleet list and on the map. Keep widgets.css and live-map.js in sync.

6 Buttons

Widget CTAs use the small button. Never a full-width button inside a dashboard widget — widgets are compact and a full-width bar reads as a page-level action.

Small (widget CTA) — height 28px · padding 5px 8px · radius 4px · Inter 13px/18px, weight 600 · fill #0067E6, label #FFF · auto width, left-aligned, pinned to the bottom of the card with margin-top:auto. Class .ew-btn.ew-btn--primary (+ .ew-btn--pin to pin).

Default (page / modal level, not inside widgets) — height 40px (--lg 48px) · radius 4px · 14–15px / 600 weight.

7 Segmented control

Switches between views of the same widget — use it instead of splitting one subject across two cards. Track rgba(0,0,75,.06), radius 8px, padding 2px, 2px gap. Item: padding 4px 10px, radius 6px, Inter 12px/600. Active item: #FFF fill, navy label, 0 1px 3px rgba(0,0,75,.12). Sits top-right of the card header. Classes .ew-seg / .ew-seg-btn.is-active.

When a view needs the whole card (e.g. Fleet status → Map), the card drops its padding and the control floats over the content. Keep the track opaque there — use #F0F0F4 (what the normal track composites to on white) plus a stronger shadow. A translucent white track makes the white active pill disappear.

8 Amounts, sections & share bars

Money is always written in full. Two decimals, non-breaking thin space as the thousands separator, comma as the decimal mark, and the currency as a separate muted suffix — never baked into the bold number.

14 830,00 EUR

Hero value — Gilroy 600, 32px, #00004B. Currency Inter 14px rgba(0,0,75,.5). Row value — Inter 600, 14px; currency 12px, same muted navy, margin-left:4px.

Status rows sit directly on the card — no grey container. A single 1px rgba(0,0,75,.10) rule separates the summary from the breakdown below it, and the section label is sentence case, Inter 600 13px rgba(0,0,75,.55) — not uppercase, not letterspaced.

Settled (paid out)9 200,00EUR
Top countries
Germany
5 400,00EUR
Austria
3 600,00EUR
France
2 500,00EUR
Others
3 330,00EUR

Share bar — use a single 100%-stacked bar, not one bar per row, whenever the entries add up to the headline figure (they do here: 5 400 + 3 600 + 2 500 + 3 330 = 14 830). It answers “what share of my refund came from where?” in one glance, and costs about a third of the height of stacked rows. Four bars side by side answer only “which is biggest”, which the ordering already told you. If the parts do not sum to the whole, do not use it — use ranked rows.

It is literally the same component as the Available-balance bar — one .ew-share / .ew-share-bar / .ew-share-seg / .ew-share-hit set, shared by both widgets, with one hover script over both. 14px tall, fully rounded: a track carrying the last colour, with cumulative fills absolutely positioned on top, widest first, each a full pill with box-shadow: 0 0 0 3px #fff. Two-segment (Available / Spent) and four-segment (Top countries) are the same code with different data.

The box-shadow follows the fill's border-radius, so the separator is a 3px crescent hugging the rounded cap and the next colour reads with a concave edge. A flex gap between four separate pills looks close but is subtly wrong — it gives a straight vertical slit, and the two bars stop matching. Widths are cumulative percentages (36.41 / 60.69 / 77.55 / 100), not per-segment.

Amounts are revealed on hover, from either the legend label or its band in the bar; the matching segment keeps its colour while the rest drop to a flat --ew-cat-mute. At rest the widget stays quiet — the bar already carries the shape, and extra figures competing with the hero total are noise. The revealed amount sits under its label, not inline beside it: inline would reserve width next to the label and stop a right-aligned legend entry (“Spent”) sitting flush with the card edge at rest.

Two implementation notes. The hidden amount stays in flow at opacity:0, so revealing it never shifts the layout. And the muted state swaps a solid colour (each fill carries its own in --c) rather than using opacity or filter — the fills overlap, so anything semi-transparent lets the colour beneath bleed through. Hover is driven by transparent hit slices layered over the fills, each covering only its own visible band; without them the cursor would always land on the widest pill. click is bound alongside mouseenter so the same interaction works on touch.

Show real amounts, not percentages. A percentage is derivable from the bar itself; the money is the thing the user actually came for. The bar carries the proportion, the legend carries the value.

Any column of figures uses tabular numerals — font-variant-numeric: tabular-nums. In the default proportional figures a 1 is narrower than a 5, so prices and amounts jitter from row to row and the currency suffix stops lining up. Right-aligning alone does not fix the digits inside the number.

Country entries keep the circular 20px SVG flag (clipped SVG, 1px rgba(0,0,75,.12) hairline) — never an emoji, which renders differently per OS. Tinting that hairline with the series colour was tried and rejected: a coloured ring fights the flag's own colours instead of keying it. The legend maps to the bar by order plus hover, not by a swatch.

Categorical palette (--ew-cat-*) — for parts-of-a-whole charts only, never for status, where color already carries meaning. #0067E6 → #2AABEB → #7B61D9, then #C7C7D4 for the residual “Others” bucket. Two of the three are brand colors already in use; “Others” stays neutral so a catch-all never reads as a top performer. Assign in rank order, largest first.

9 Icon library

All icons are 24×24 filled paths — no strokes, no outline/filled mix. Never redraw one by hand: copy the path. Vehicle-status paths are the single source of truth in live-map.js (NAV_PATH / PAUSE_PATH / STOP_PATH) and are reused by the map pins, the fleet list and the Fleet status tiles.

Vehicle status — always reversed out in white on a 36px colored bubble.

Driving
Idling
Stopped
Offline

Claim status — full-color glyph, no tinted bubble behind it. The color is the status.

Settled
Submitted
In progress

Neutral / list — #00004B at fill-opacity .54, so they read as structure rather than status.

Fuel station

Sizes in use: 24px standalone (claim status rows) · 22px inside a 36px status bubble · 20px as a list-row leading icon. Below 20px the detail in these paths breaks down — use a larger size rather than a simpler shape.

10 Do / Don't

11 Next steps

  1. Add a "chart/graph" widget pattern once a concept needs one (none yet).
  2. Document empty/loading/error states per widget (not yet designed).
  3. Fold this into projects/design-system/design-system.md once the dashboard patterns stabilize across concepts.