Financial UI Patterns
Overview
Generic 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.
This skill codifies the patterns that production trading UIs (Kraken, Coinbase, TradingView, Bloomberg Terminal, Robinhood, Binance) actually ship.
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.
When to Use
- Tables with prices, P&L, holdings, positions, orders, fills, trades
- Order books, depth charts, ladders
- Watchlists, tickers, market overviews
- Streaming/live data of any kind
- Portfolio screens, transaction history
- Charts with OHLC, candlesticks, sparklines
- Any UI showing money, percentages, basis points, or quantities
Don't use for: marketing pages, blog posts, generic product UI without numbers.
Quick Reference
| Concern |
Rule |
| Number rendering |
Always tabular-nums. Never let digits reflow. |
| Number alignment |
Right-align in tables. Decimal-align when precision varies. |
| Ticker/ID display |
Use font-mono (JetBrains Mono, Roboto Mono, IBM Plex Mono). |
| Colors |
Semantic tokens only (text-positive, text-negative). Never text-green-500. |
| Theme |
Light + dark via CSS variables. Never hard-code surface colors. |
| Tick flash |
300-500ms tinted background on price update. CSS only, no JS animation. |
| P&L sign |
Always show + for positive returns. - is automatic on negatives. |
| Tailwind dynamic |
NEVER bg-${color}-500/10. Use static class maps. |
| Streaming state |
Show connecting / live / stale / disconnected explicitly. |
| Accessibility |
Pair color with icon, position, or shape. Never color alone. |
| Decimals |
Crypto: dynamic precision by price magnitude. Stocks: 2 dp. FX: 4-5 dp. |
| Large numbers |
Compact notation (1.2B, 847M) for caps/volume, not for prices/balances. |
| Density |
Default to compact. 32-40px row height in tables. 24-28px in order books. |
Required Reading
| File |
When to load |
references/typography-and-color.md |
Designing any financial surface from scratch |
references/number-formatting.md |
Implementing any price/quantity/percentage display |
references/components.md |
Building tables, order books, tickers, charts |
references/streaming-and-state.md |
Live data, WebSocket UIs, tick-flash, staleness |
references/accessibility.md |
Any production UI (always) |
references/mobile-and-responsive.md |
Targeting phones/tablets, responsive tables, bottom sheets, touch interactions |
references/industry-patterns.md |
Want specific references from Kraken/Coinbase/TradingView/Bloomberg |
references/charts-and-candles.md |
Building any chart (candles, OHLC, line, volume, indicators) |
references/loading-and-skeletons.md |
First-load and reconnect treatments for tables, order books, charts |
references/empty-and-error-states.md |
Empty positions/orders, rejected orders, market closed, rate-limit copy |
references/timestamps-and-timezones.md |
Trade times, "as of" stamps, multi-TZ status bar, ms precision rules |
references/virtualization.md |
Tables over 100 streaming rows, trades tape, sticky headers, row memoization |
references/chart-interactions.md |
Crosshair, zoom/pan, drawing tools, multi-pane stacks, number animations |
references/order-entry-and-lifecycle.md |
Order forms, types (market/limit/stop/bracket/OCO), preview-then-submit, pending→filled states, time-and-sales |
references/alerts-and-disclosures.md |
Price alerts, fill notifications, escalation rules, PDT/wash-sale/options/restricted-symbol disclosures |
references/data-sources-and-freshness.md |
Real-time/delayed/stale/frozen/disconnected chain, source chips (CBOE, NBBO), multi-account context |
references/heatmaps-and-density-viz.md |
Sector heatmaps, options chain color scales, IV surfaces, correlation grids |
Core Pattern: Numbers
The single most important fix vs. generic AI output:
// ❌ BAD: digits shift width on update, color is decorative, no sign on positive
<span className="text-green-500 font-medium">
{value.toFixed(2)}%
</span>
// ✅ GOOD: tabular-nums locks width, semantic color, explicit sign, fixed width
<span
className={`tabular-nums font-medium tracking-tight ${
value >= 0 ? "text-positive" : "text-negative"
}`}
style={{ minWidth: 64, textAlign: "right" }}
>
{value >= 0 ? "+" : ""}{value.toFixed(2)}%
</span>
Three failures the bad version causes:
- Width changes when value goes from
9.99% to 10.00% — the entire row reflows
text-green-500 breaks light theme and ignores any design system
- No
+ prefix means positive and zero look identical at a glance
Core Pattern: Color Tokens
Define semantic colors via CSS variables, expose via Tailwind, never use raw color values in components.
/* globals.css */
:root {
--positive: 34 197 94; /* green for gains, buy, success */
--negative: 239 68 68; /* red for losses, sell, danger */
--warning: 251 191 36; /* amber for partial fills, stale data */
--info: 0 143 250; /* blue for working orders, neutral signals */
--surface: 13 13 16;
--surface-elevated: 22 22 26;
--text-primary: 240 240 245;
--text-secondary: 180 180 192;
--text-muted: 110 110 125;
}
[data-theme="light"] {
--positive: 22 163 74;
--negative: 220 38 38;
--surface: 248 249 251;
/* ... */
}
Tailwind v3 form (tailwind.config.ts):
colors: {
positive: "rgb(var(--positive) / <alpha-value>)",
negative: "rgb(var(--negative) / <alpha-value>)",
surface: {
DEFAULT: "rgb(var(--surface) / <alpha-value>)",
elevated: "rgb(var(--surface-elevated) / <alpha-value>)",
},
text: {
primary: "rgb(var(--text-primary) / <alpha-value>)",
secondary: "rgb(var(--text-secondary) / <alpha-value>)",
muted: "rgb(var(--text-muted) / <alpha-value>)",
},
}
Tailwind v4 form (in your CSS, no config file needed):
@theme {
--color-positive: rgb(var(--positive));
--color-negative: rgb(var(--negative));
--color-surface: rgb(var(--surface));
--color-surface-elevated: rgb(var(--surface-elevated));
--color-text-primary: rgb(var(--text-primary));
--color-text-secondary: rgb(var(--text-secondary));
--color-text-muted: rgb(var(--text-muted));
}
Core Pattern: Tick Flash
When a price updates, briefly tint the background. CSS only. Robinhood, Coinbase, and TradingView all use this.
// PriceCell.tsx
import { useEffect, useRef, useState } from "react";
export function PriceCell({ value, format }: { value: number; format: (n: number) => string }) {
const prev = useRef(value);
const [flash, setFlash] = useState<"up" | "down" | null>(null);
useEffect(() => {
if (value === prev.current) return;
setFlash(value > prev.current ? "up" : "down");
prev.current = value;
const t = setTimeout(() => setFlash(null), 400);
return () => clearTimeout(t);
}, [value]);
return (
<span
data-flash={flash ?? undefined}
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"
>
{format(value)}
</span>
);
}
Why this beats animation libraries: zero JS during the flash, scales to hundreds of cells, no jank on heavy WebSocket traffic.
Core Pattern: Decimal Alignment
tabular-nums aligns digit positions but does not align decimal points across different-magnitude values. Two fixes:
Option A — pad with hair spaces (Bloomberg approach):
function alignDecimal(value: number, totalDigits = 8): string {
const s = value.toFixed(2);
const [int] = s.split(".");
const padNeeded = totalDigits - int.length;
return " ".repeat(padNeeded) + s; // hair space, non-breaking
}
Option B — fixed-width columns with right-align (TradingView approach):
<div className="grid grid-cols-[1fr_120px_120px_120px] gap-2 tabular-nums">
<div>{symbol}</div>
<div className="text-right">{formatPrice(price)}</div>
<div className="text-right">{formatQty(qty)}</div>
<div className="text-right">{formatValue(value)}</div>
</div>
Use B by default. Use A only when values must fit in flowing text.
Common Mistakes
| Mistake |
Why it breaks |
Fix |
text-green-500 / text-red-500 |
Doesn't respect theme, no light/dark, generic look |
Semantic tokens: text-positive / text-negative |
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] |
font-medium numbers without tabular-nums |
Digits shift width on every update |
Always pair tabular-nums with any updating number |
Hard-coded bg-zinc-950 |
Breaks light theme |
Use bg-surface from CSS variable tokens |
text-emerald-400 for prices |
Color overload, makes deltas harder to spot |
Color only deltas/signals. Keep prices neutral. |
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 |
| Re-rendering whole row on tick |
Drops frames at 100+ symbols streaming |
Isolate updating cell, memoize the rest |
| No staleness indicator |
Users trade on dead data |
Show grey/dim state when last update > N seconds |
| Color-only gain/loss |
8% of men can't distinguish red/green |
Add ▲ ▼ arrows or +/- prefix |
| Animation libraries for ticks |
Bundle bloat, GPU thrash at scale |
CSS transitions on data-* attributes |
text-zinc-500 for axis labels |
Generic AI tell |
Use text-text-muted token |
| Centered numbers in tables |
Eye can't compare magnitudes |
Right-align numbers, left-align labels |
Red Flags — Stop and Reread
When writing financial UI you may rationalize. These mean stop:
- "It's just a prototype, I'll fix tokens later" — semantic tokens take the same time to write as raw colors. Write them now.
- "The user won't notice digit shift" — they will, especially on a watchlist. It's the #1 tell of an unprofessional trading UI.
- "Tailwind should expand
bg-${color}-500/10 since the strings exist somewhere" — JIT does not scan dynamic templates. Use a static map.
- "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.
- "Mono font for tickers looks dated" — it's the opposite. Bloomberg, Reuters, TradingView all use mono for tickers/symbols. It's a professionalism signal.
- "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.
Industry Reference Points
| Pattern |
Who does it best |
Why study it |
| Order book depth bars (asks fill right→left, bids left→right) |
Kraken, Binance, Coinbase Pro |
Standard convention; users expect it |
| Compact density toggle |
TradingView, Bloomberg |
Pro users want maximum data per screen |
| Tick-flash on price cells |
Robinhood, Coinbase, Massive dashboards |
Confirms data is live without distraction |
| OHLC color coding (green hollow / red filled candles) |
TradingView |
Industry standard candle styling |
| Status pills for orders (filled/partial/working/cancelled/rejected) |
Interactive Brokers, Coinbase Pro |
Lets traders scan order state in milliseconds |
| Sparklines next to prices |
Robinhood, Yahoo Finance, Massive |
Compact trend signal without consuming chart space |
| "Last / Mark / Index" price split for derivatives |
Binance, Bybit, dYdX |
Critical for liquidation awareness |
| Significant-figure pricing for low-value tokens |
Coinbase, CoinGecko |
$0.00001234 is readable; $0.00 is broken |
| Color-blind safe pairs (blue/orange) as alternative to red/green |
Bloomberg accessibility mode |
Required for accessibility-serious products |
See references/industry-patterns.md for screenshots' worth of detail on each.
Verification
Before claiming a financial UI is done, check:
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/mobile-and-responsive.md` | Targeting phones/tablets, responsive tables, bottom sheets, touch interactions |56| `references/industry-patterns.md` | Want specific references from Kraken/Coinbase/TradingView/Bloomberg |57| `references/charts-and-candles.md` | Building any chart (candles, OHLC, line, volume, indicators) |58| `references/loading-and-skeletons.md` | First-load and reconnect treatments for tables, order books, charts |59| `references/empty-and-error-states.md` | Empty positions/orders, rejected orders, market closed, rate-limit copy |60| `references/timestamps-and-timezones.md` | Trade times, "as of" stamps, multi-TZ status bar, ms precision rules |61| `references/virtualization.md` | Tables over 100 streaming rows, trades tape, sticky headers, row memoization |62| `references/chart-interactions.md` | Crosshair, zoom/pan, drawing tools, multi-pane stacks, number animations |63| `references/order-entry-and-lifecycle.md` | Order forms, types (market/limit/stop/bracket/OCO), preview-then-submit, pending→filled states, time-and-sales |64| `references/alerts-and-disclosures.md` | Price alerts, fill notifications, escalation rules, PDT/wash-sale/options/restricted-symbol disclosures |65| `references/data-sources-and-freshness.md` | Real-time/delayed/stale/frozen/disconnected chain, source chips (CBOE, NBBO), multi-account context |66| `references/heatmaps-and-density-viz.md` | Sector heatmaps, options chain color scales, IV surfaces, correlation grids |6768## Core Pattern: Numbers6970The single most important fix vs. generic AI output:7172```tsx73// ❌ BAD: digits shift width on update, color is decorative, no sign on positive74<span className="text-green-500 font-medium">75 {value.toFixed(2)}%76</span>7778// ✅ GOOD: tabular-nums locks width, semantic color, explicit sign, fixed width79<span80 className={`tabular-nums font-medium tracking-tight ${81 value >= 0 ? "text-positive" : "text-negative"82 }`}83 style={{ minWidth: 64, textAlign: "right" }}84>85 {value >= 0 ? "+" : ""}{value.toFixed(2)}%86</span>87```8889Three failures the bad version causes:901. Width changes when value goes from `9.99%` to `10.00%` — the entire row reflows912. `text-green-500` breaks light theme and ignores any design system923. No `+` prefix means positive and zero look identical at a glance9394## Core Pattern: Color Tokens9596Define semantic colors via CSS variables, expose via Tailwind, never use raw color values in components.9798```css99/* globals.css */100:root {101 --positive: 34 197 94; /* green for gains, buy, success */102 --negative: 239 68 68; /* red for losses, sell, danger */103 --warning: 251 191 36; /* amber for partial fills, stale data */104 --info: 0 143 250; /* blue for working orders, neutral signals */105 --surface: 13 13 16;106 --surface-elevated: 22 22 26;107 --text-primary: 240 240 245;108 --text-secondary: 180 180 192;109 --text-muted: 110 110 125;110}111112[data-theme="light"] {113 --positive: 22 163 74;114 --negative: 220 38 38;115 --surface: 248 249 251;116 /* ... */117}118```119120**Tailwind v3 form** (`tailwind.config.ts`):121```ts122colors: {123 positive: "rgb(var(--positive) / <alpha-value>)",124 negative: "rgb(var(--negative) / <alpha-value>)",125 surface: {126 DEFAULT: "rgb(var(--surface) / <alpha-value>)",127 elevated: "rgb(var(--surface-elevated) / <alpha-value>)",128 },129 text: {130 primary: "rgb(var(--text-primary) / <alpha-value>)",131 secondary: "rgb(var(--text-secondary) / <alpha-value>)",132 muted: "rgb(var(--text-muted) / <alpha-value>)",133 },134}135```136137**Tailwind v4 form** (in your CSS, no config file needed):138```css139@theme {140 --color-positive: rgb(var(--positive));141 --color-negative: rgb(var(--negative));142 --color-surface: rgb(var(--surface));143 --color-surface-elevated: rgb(var(--surface-elevated));144 --color-text-primary: rgb(var(--text-primary));145 --color-text-secondary: rgb(var(--text-secondary));146 --color-text-muted: rgb(var(--text-muted));147}148```149150## Core Pattern: Tick Flash151152When a price updates, briefly tint the background. CSS only. Robinhood, Coinbase, and TradingView all use this.153154```tsx155// PriceCell.tsx156import { useEffect, useRef, useState } from "react";157158export function PriceCell({ value, format }: { value: number; format: (n: number) => string }) {159 const prev = useRef(value);160 const [flash, setFlash] = useState<"up" | "down" | null>(null);161162 useEffect(() => {163 if (value === prev.current) return;164 setFlash(value > prev.current ? "up" : "down");165 prev.current = value;166 const t = setTimeout(() => setFlash(null), 400);167 return () => clearTimeout(t);168 }, [value]);169170 return (171 <span172 data-flash={flash ?? undefined}173 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"174 >175 {format(value)}176 </span>177 );178}179```180181Why this beats animation libraries: zero JS during the flash, scales to hundreds of cells, no jank on heavy WebSocket traffic.182183## Core Pattern: Decimal Alignment184185`tabular-nums` aligns digit positions but does not align decimal points across different-magnitude values. Two fixes:186187**Option A — pad with hair spaces (Bloomberg approach):**188```ts189function alignDecimal(value: number, totalDigits = 8): string {190 const s = value.toFixed(2);191 const [int] = s.split(".");192 const padNeeded = totalDigits - int.length;193 return " ".repeat(padNeeded) + s; // hair space, non-breaking194}195```196197**Option B — fixed-width columns with right-align (TradingView approach):**198```tsx199<div className="grid grid-cols-[1fr_120px_120px_120px] gap-2 tabular-nums">200 <div>{symbol}</div>201 <div className="text-right">{formatPrice(price)}</div>202 <div className="text-right">{formatQty(qty)}</div>203 <div className="text-right">{formatValue(value)}</div>204</div>205```206207Use B by default. Use A only when values must fit in flowing text.208209## Common Mistakes210211| Mistake | Why it breaks | Fix |212|---|---|---|213| `text-green-500` / `text-red-500` | Doesn't respect theme, no light/dark, generic look | Semantic tokens: `text-positive` / `text-negative` |214| `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]` |215| `font-medium` numbers without `tabular-nums` | Digits shift width on every update | Always pair `tabular-nums` with any updating number |216| Hard-coded `bg-zinc-950` | Breaks light theme | Use `bg-surface` from CSS variable tokens |217| `text-emerald-400` for prices | Color overload, makes deltas harder to spot | Color only deltas/signals. Keep prices neutral. |218| `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` |219| Re-rendering whole row on tick | Drops frames at 100+ symbols streaming | Isolate updating cell, memoize the rest |220| No staleness indicator | Users trade on dead data | Show grey/dim state when last update > N seconds |221| Color-only gain/loss | 8% of men can't distinguish red/green | Add ▲ ▼ arrows or +/- prefix |222| Animation libraries for ticks | Bundle bloat, GPU thrash at scale | CSS transitions on `data-*` attributes |223| `text-zinc-500` for axis labels | Generic AI tell | Use `text-text-muted` token |224| Centered numbers in tables | Eye can't compare magnitudes | Right-align numbers, left-align labels |225226## Red Flags — Stop and Reread227228When writing financial UI you may rationalize. These mean stop:229230- "It's just a prototype, I'll fix tokens later" — semantic tokens take the same time to write as raw colors. Write them now.231- "The user won't notice digit shift" — they will, especially on a watchlist. It's the #1 tell of an unprofessional trading UI.232- "Tailwind should expand `bg-${color}-500/10` since the strings exist somewhere" — JIT does not scan dynamic templates. Use a static map.233- "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.234- "Mono font for tickers looks dated" — it's the opposite. Bloomberg, Reuters, TradingView all use mono for tickers/symbols. It's a professionalism signal.235- "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.236237## Industry Reference Points238239| Pattern | Who does it best | Why study it |240|---|---|---|241| Order book depth bars (asks fill right→left, bids left→right) | Kraken, Binance, Coinbase Pro | Standard convention; users expect it |242| Compact density toggle | TradingView, Bloomberg | Pro users want maximum data per screen |243| Tick-flash on price cells | Robinhood, Coinbase, Massive dashboards | Confirms data is live without distraction |244| OHLC color coding (green hollow / red filled candles) | TradingView | Industry standard candle styling |245| Status pills for orders (filled/partial/working/cancelled/rejected) | Interactive Brokers, Coinbase Pro | Lets traders scan order state in milliseconds |246| Sparklines next to prices | Robinhood, Yahoo Finance, Massive | Compact trend signal without consuming chart space |247| "Last / Mark / Index" price split for derivatives | Binance, Bybit, dYdX | Critical for liquidation awareness |248| Significant-figure pricing for low-value tokens | Coinbase, CoinGecko | `$0.00001234` is readable; `$0.00` is broken |249| Color-blind safe pairs (blue/orange) as alternative to red/green | Bloomberg accessibility mode | Required for accessibility-serious products |250251See `references/industry-patterns.md` for screenshots' worth of detail on each.252253## Verification254255Before claiming a financial UI is done, check:256257- [ ] Every number has `tabular-nums`258- [ ] Every color is a semantic token, no raw `text-green-*` / `bg-zinc-*`259- [ ] Light theme renders correctly (toggle and verify)260- [ ] Streaming prices flash on update261- [ ] Stale data state is visible (e.g., dim or grey when > N seconds old)262- [ ] Positive returns show `+` prefix263- [ ] Numbers are right-aligned, labels are left-aligned264- [ ] Tickers use mono font265- [ ] No `bg-${var}` or other dynamic Tailwind classes266- [ ] Color is paired with non-color signal (arrow, icon, sign, position)267- [ ] Order/trade states use status pills, not just text color268- [ ] At least 5 rows visible without scroll on a typical laptop screen (density check)