Building a trading UI looks straightforward on the surface. You need a chart, an order book, a trade blotter, and some forms. A capable team could prototype this in a few weeks.
Then reality hits. The gap between a prototype and a production trading frontend is enormous - not because of the big features, but because of the dozens of details that seem trivial until you get them wrong.
Number formatting is harder than you think
A trading interface displays numbers everywhere: prices, quantities, P&L, percentages, volumes. Each needs specific formatting:
- Prices must match the instrument's tick size. BTC/USD might show 2 decimal places. BTC/USDT on a perpetual might show 1. A low-cap altcoin might need 8.
- Quantities follow different precision rules than prices, often defined per instrument.
- P&L needs sign indicators, colour coding, and potentially currency symbols.
- Large numbers need grouping separators that vary by locale (1,000,000 vs 1.000.000).
The naive approach of toFixed(2) everywhere breaks immediately. You need a formatting system that is instrument-aware and consistent across every component.
function formatPrice(value: number, instrument: Instrument): string { return value.toFixed(instrument.pricePrecision); } function formatQuantity(value: number, instrument: Instrument): string { return value.toFixed(instrument.quantityPrecision); }
Simple, but this logic needs to be available everywhere - order forms, blotters, P&L displays, tooltips, chart overlays. Inconsistent formatting across components erodes trust instantly.
Timezone handling
Traders operate across timezones. A London trader looking at candle close times needs to know whether those timestamps are in UTC, exchange local time, or their own timezone.
Common mistakes:
- Displaying timestamps without timezone context
- Converting everything to the user's local timezone (which breaks when discussing times with colleagues in other zones)
- Using JavaScript's
Dateobject without accounting for daylight saving transitions - Chart candles aligned to UTC midnight vs exchange midnight vs user midnight
The solution is to store all timestamps in UTC, display them in the user's chosen timezone, and always show the timezone abbreviation alongside any time display.
Order state machines are complex
An order has a lifecycle: created, submitted, acknowledged, partially filled, fully filled, cancelled, rejected. Each state transition affects the UI differently:
- A pending order shows in the "working orders" blotter
- A partial fill updates the remaining quantity and may trigger a notification
- A rejection needs to surface the reason and allow re-submission
- A cancel needs optimistic UI updates followed by confirmation
Most teams model this as a simple status: string field. What you actually need is a state machine that defines valid transitions and prevents impossible states:
type OrderState = | { status: "pending" } | { status: "submitted"; exchangeId: string } | { status: "partial"; filledQuantity: number; remainingQuantity: number } | { status: "filled"; filledQuantity: number; avgPrice: number } | { status: "cancelled"; reason?: string } | { status: "rejected"; reason: string };
Discriminated unions in TypeScript make impossible states unrepresentable. This prevents an entire class of bugs where the UI tries to display fill information for an order that was never acknowledged.
Keyboard shortcuts are expected
Professional traders use keyboard shortcuts for everything. They expect:
- Number keys to set quantity presets
- Enter to submit orders
- Escape to cancel or close modals
- Arrow keys to adjust prices by tick increments
- Hotkeys for switching between instruments
This is not a nice-to-have feature. Traders who cannot place orders without reaching for the mouse will not use your platform. Building a robust keyboard shortcut system that does not conflict with browser defaults and works across focusable elements is a non-trivial engineering effort.
Error states are critical
In a trading application, errors have financial consequences. The UI must communicate:
- Connection status - is the WebSocket feed alive? Is it reconnecting? How stale is the data?
- Order errors - insufficient balance, price outside limits, instrument halted
- Rate limits - many exchanges throttle API calls. The UI needs to queue or block actions gracefully.
- Data inconsistency - when the order book and recent trades disagree, which should the trader trust?
Every error state needs a clear, non-blocking notification mechanism. Modal dialogs that require a click to dismiss are unacceptable in a trading context - they interrupt the workflow at the worst possible moment.
Performance under stress
The irony of trading UIs is that performance matters most when it is hardest to achieve. During a market crash or a major news event:
- WebSocket message rates spike 10–50x
- Users interact more rapidly (placing and cancelling orders)
- Multiple instruments move simultaneously
- The order book reshuffles entirely in seconds
If your UI has not been tested under simulated market stress, it will fail when it matters most. Build load testing into your development process - replay high-activity WebSocket recordings and verify that the UI remains responsive.
The takeaway
The features that make a trading UI production-ready are not the chart or the order book. They are number formatting, timezone handling, order state management, keyboard shortcuts, error handling, and performance under stress. These "details" account for 60–70% of the total development effort.
Teams that underestimate this end up either shipping a fragile product or rebuilding significant portions of the frontend after launch. Starting with a proven foundation - one that has already solved these problems - compresses months of hidden work into days of customisation.