Use when building any UI that displays prices, P&L, holdings, orders, trades, charts, order books, watchlists, or streaming market data. Covers patterns from Kraken, Coinbase, TradingView, Bloomberg, Robinhood. Read before writing JSX for any financial surface.
Add this skill
npx mdskills install rgourley/financial-ui-patternsComprehensive financial UI pattern library with production-grade number formatting, theming, and accessibility rules
1---2name: financial-ui-patterns3description: Use when building any UI that displays prices, P&L, holdings, orders, trades, charts, order books, watchlists, or streaming market data. Covers patterns from Kraken, Coinbase, TradingView, Bloomberg, Robinhood. Read before writing JSX for any financial surface.4---56# Financial UI Patterns78## Overview910Generic AI output for financial UIs fails in predictable ways: raw color values instead of design tokens, jittery non-tabular numbers, no decimal alignment, missing tick-flash on updates, hard-coded dark theme, broken Tailwind dynamic classes, no streaming/staleness states, no accessibility for color-blind users.1112This skill codifies the patterns that production trading UIs (Kraken, Coinbase, TradingView, Bloomberg Terminal, Robinhood, Binance) actually ship.1314**Core principle:** numbers must be legible, aligned, and trustworthy. Color is a signal, not decoration. Latency must be visible. Every digit shift is a failure.1516## When to Use1718- Tables with prices, P&L, holdings, positions, orders, fills, trades19- Order books, depth charts, ladders20- Watchlists, tickers, market overviews21- Streaming/live data of any kind22- Portfolio screens, transaction history23- Charts with OHLC, candlesticks, sparklines24- Any UI showing money, percentages, basis points, or quantities2526**Don't use for:** marketing pages, blog posts, generic product UI without numbers.2728## Quick Reference2930| Concern | Rule |31|---|---|32| Number rendering | Always `tabular-nums`. Never let digits reflow. |33| Number alignment | Right-align in tables. Decimal-align when precision varies. |34| Ticker/ID display | Use `font-mono` (JetBrains Mono, Roboto Mono, IBM Plex Mono). |35| Colors | Semantic tokens only (`text-positive`, `text-negative`). Never `text-green-500`. |36| Theme | Light + dark via CSS variables. Never hard-code surface colors. |37| Tick flash | 300-500ms tinted background on price update. CSS only, no JS animation. |38| P&L sign | Always show `+` for positive returns. `-` is automatic on negatives. |39| Tailwind dynamic | NEVER `bg-${color}-500/10`. Use static class maps. |40| Streaming state | Show connecting / live / stale / disconnected explicitly. |41| Accessibility | Pair color with icon, position, or shape. Never color alone. |42| Decimals | Crypto: dynamic precision by price magnitude. Stocks: 2 dp. FX: 4-5 dp. |43| Large numbers | Compact notation (`1.2B`, `847M`) for caps/volume, not for prices/balances. |44| Density | Default to compact. 32-40px row height in tables. 24-28px in order books. |4546## Required Reading4748| File | When to load |49|---|---|50| `references/typography-and-color.md` | Designing any financial surface from scratch |51| `references/number-formatting.md` | Implementing any price/quantity/percentage display |52| `references/components.md` | Building tables, order books, tickers, charts |53| `references/streaming-and-state.md` | Live data, WebSocket UIs, tick-flash, staleness |54| `references/accessibility.md` | Any production UI (always) |55| `references/industry-patterns.md` | Want specific references from Kraken/Coinbase/TradingView/Bloomberg |5657## Core Pattern: Numbers5859The single most important fix vs. generic AI output:6061```tsx62// ❌ BAD: digits shift width on update, color is decorative, no sign on positive63<span className="text-green-500 font-medium">64 {value.toFixed(2)}%65</span>6667// ✅ GOOD: tabular-nums locks width, semantic color, explicit sign, fixed width68<span69 className={`tabular-nums font-medium tracking-tight ${70 value >= 0 ? "text-positive" : "text-negative"71 }`}72 style={{ minWidth: 64, textAlign: "right" }}73>74 {value >= 0 ? "+" : ""}{value.toFixed(2)}%75</span>76```7778Three failures the bad version causes:791. Width changes when value goes from `9.99%` to `10.00%` — the entire row reflows802. `text-green-500` breaks light theme and ignores any design system813. No `+` prefix means positive and zero look identical at a glance8283## Core Pattern: Color Tokens8485Define semantic colors via CSS variables, expose via Tailwind, never use raw color values in components.8687```css88/* globals.css */89:root {90 --positive: 34 197 94; /* green for gains, buy, success */91 --negative: 239 68 68; /* red for losses, sell, danger */92 --warning: 251 191 36; /* amber for partial fills, stale data */93 --info: 0 143 250; /* blue for working orders, neutral signals */94 --surface: 13 13 16;95 --surface-elevated: 22 22 26;96 --text-primary: 240 240 245;97 --text-secondary: 180 180 192;98 --text-muted: 110 110 125;99}100101[data-theme="light"] {102 --positive: 22 163 74;103 --negative: 220 38 38;104 --surface: 248 249 251;105 /* ... */106}107```108109```ts110// tailwind.config.ts111colors: {112 positive: "rgb(var(--positive) / <alpha-value>)",113 negative: "rgb(var(--negative) / <alpha-value>)",114 surface: {115 DEFAULT: "rgb(var(--surface) / <alpha-value>)",116 elevated: "rgb(var(--surface-elevated) / <alpha-value>)",117 },118 text: {119 primary: "rgb(var(--text-primary) / <alpha-value>)",120 secondary: "rgb(var(--text-secondary) / <alpha-value>)",121 muted: "rgb(var(--text-muted) / <alpha-value>)",122 },123}124```125126## Core Pattern: Tick Flash127128When a price updates, briefly tint the background. CSS only. Robinhood, Coinbase, and TradingView all use this.129130```tsx131// PriceCell.tsx132import { useEffect, useRef, useState } from "react";133134export function PriceCell({ value, format }: { value: number; format: (n: number) => string }) {135 const prev = useRef(value);136 const [flash, setFlash] = useState<"up" | "down" | null>(null);137138 useEffect(() => {139 if (value === prev.current) return;140 setFlash(value > prev.current ? "up" : "down");141 prev.current = value;142 const t = setTimeout(() => setFlash(null), 400);143 return () => clearTimeout(t);144 }, [value]);145146 return (147 <span148 data-flash={flash ?? undefined}149 className="tabular-nums tracking-tight transition-colors duration-300 px-1 rounded-sm data-[flash=up]:bg-positive/15 data-[flash=down]:bg-negative/15"150 >151 {format(value)}152 </span>153 );154}155```156157Why this beats animation libraries: zero JS during the flash, scales to hundreds of cells, no jank on heavy WebSocket traffic.158159## Core Pattern: Decimal Alignment160161`tabular-nums` aligns digit positions but does not align decimal points across different-magnitude values. Two fixes:162163**Option A — pad with hair spaces (Bloomberg approach):**164```ts165function alignDecimal(value: number, totalDigits = 8): string {166 const s = value.toFixed(2);167 const [int] = s.split(".");168 const padNeeded = totalDigits - int.length;169 return " ".repeat(padNeeded) + s; // hair space, non-breaking170}171```172173**Option B — fixed-width columns with right-align (TradingView approach):**174```tsx175<div className="grid grid-cols-[1fr_120px_120px_120px] gap-2 tabular-nums">176 <div>{symbol}</div>177 <div className="text-right">{formatPrice(price)}</div>178 <div className="text-right">{formatQty(qty)}</div>179 <div className="text-right">{formatValue(value)}</div>180</div>181```182183Use B by default. Use A only when values must fit in flowing text.184185## Common Mistakes186187| Mistake | Why it breaks | Fix |188|---|---|---|189| `text-green-500` / `text-red-500` | Doesn't respect theme, no light/dark, generic look | Semantic tokens: `text-positive` / `text-negative` |190| `bg-${color}-500/10` dynamic class | Tailwind JIT strips it, renders without bg | Static class map: `{ up: "bg-positive/10", down: "bg-negative/10" }[dir]` |191| `font-medium` numbers without `tabular-nums` | Digits shift width on every update | Always pair `tabular-nums` with any updating number |192| Hard-coded `bg-zinc-950` | Breaks light theme | Use `bg-surface` from CSS variable tokens |193| `text-emerald-400` for prices | Color overload, makes deltas harder to spot | Color only deltas/signals. Keep prices neutral. |194| `toFixed(2)` for all prices | Wrong for BTC ($100K, 0 dp needed) and SHIB ($0.00001, 8 dp needed) | Magnitude-aware: see `references/number-formatting.md` |195| Re-rendering whole row on tick | Drops frames at 100+ symbols streaming | Isolate updating cell, memoize the rest |196| No staleness indicator | Users trade on dead data | Show grey/dim state when last update > N seconds |197| Color-only gain/loss | 8% of men can't distinguish red/green | Add ▲ ▼ arrows or +/- prefix |198| Animation libraries for ticks | Bundle bloat, GPU thrash at scale | CSS transitions on `data-*` attributes |199| `text-zinc-500` for axis labels | Generic AI tell | Use `text-text-muted` token |200| Centered numbers in tables | Eye can't compare magnitudes | Right-align numbers, left-align labels |201202## Red Flags — Stop and Reread203204When writing financial UI you may rationalize. These mean stop:205206- "It's just a prototype, I'll fix tokens later" — semantic tokens take the same time to write as raw colors. Write them now.207- "The user won't notice digit shift" — they will, especially on a watchlist. It's the #1 tell of an unprofessional trading UI.208- "Tailwind should expand `bg-${color}-500/10` since the strings exist somewhere" — JIT does not scan dynamic templates. Use a static map.209- "Light theme isn't needed" — Bloomberg, IBKR, and TradingView all ship both. Light theme is a serious-trader signal. Build with CSS variables from day one.210- "Mono font for tickers looks dated" — it's the opposite. Bloomberg, Reuters, TradingView all use mono for tickers/symbols. It's a professionalism signal.211- "I don't need a tick flash for a non-realtime page" — every list of prices that ever updates should flash. Even hourly updates benefit.212213## Industry Reference Points214215| Pattern | Who does it best | Why study it |216|---|---|---|217| Order book depth bars (asks fill right→left, bids left→right) | Kraken, Binance, Coinbase Pro | Standard convention; users expect it |218| Compact density toggle | TradingView, Bloomberg | Pro users want maximum data per screen |219| Tick-flash on price cells | Robinhood, Coinbase, Massive dashboards | Confirms data is live without distraction |220| OHLC color coding (green hollow / red filled candles) | TradingView | Industry standard candle styling |221| Status pills for orders (filled/partial/working/cancelled/rejected) | Interactive Brokers, Coinbase Pro | Lets traders scan order state in milliseconds |222| Sparklines next to prices | Robinhood, Yahoo Finance, Massive | Compact trend signal without consuming chart space |223| "Last / Mark / Index" price split for derivatives | Binance, Bybit, dYdX | Critical for liquidation awareness |224| Significant-figure pricing for low-value tokens | Coinbase, CoinGecko | `$0.00001234` is readable; `$0.00` is broken |225| Color-blind safe pairs (blue/orange) as alternative to red/green | Bloomberg accessibility mode | Required for accessibility-serious products |226227See `references/industry-patterns.md` for screenshots' worth of detail on each.228229## Verification230231Before claiming a financial UI is done, check:232233- [ ] Every number has `tabular-nums`234- [ ] Every color is a semantic token, no raw `text-green-*` / `bg-zinc-*`235- [ ] Light theme renders correctly (toggle and verify)236- [ ] Streaming prices flash on update237- [ ] Stale data state is visible (e.g., dim or grey when > N seconds old)238- [ ] Positive returns show `+` prefix239- [ ] Numbers are right-aligned, labels are left-aligned240- [ ] Tickers use mono font241- [ ] No `bg-${var}` or other dynamic Tailwind classes242- [ ] Color is paired with non-color signal (arrow, icon, sign, position)243- [ ] Order/trade states use status pills, not just text color244- [ ] At least 5 rows visible without scroll on a typical laptop screen (density check)245
Full transparency — inspect the skill content before installing.