Skip to main content

Chrome Slots

Theme tokens set the chart's colors. Chrome slots are the other half: they attach your own class or style to the chart's own furniture — the shell, the toolbars, the pane header, and the two scales.

The point is reach without risk. You get to style those surfaces without forking the component, and without descendant CSS that breaks on the next release.

TradeScript terminal showing chart shell, period bar, drawing sidebar, pane headers, price scale, time scale, watchlist, ticket, and account regions
Chrome anatomy: chart-owned regions stay inside the center chart; standalone widgets and host panels surround it. A slot changes one labeled region, not the whole terminal.

Region to API

Visible regionPrimary slot/customizationKeep owned semantics
Chart shellchart-shellMount, focus boundary, chart lifecycle
Top period barperiod-barBuilt-in actions, state, responsive overflow
Drawing raildrawing-sidebarTool IDs, active/disabled state
Pane headerpane-headerIndicator identity, values, pane actions
Price/time scaleprice-scale / time-scaleCoordinates, labels, interaction
Standalone widget rowFormatter or documented render* propSelection, keyboard, subscriptions

Anatomy: two close-ups

TradeScript top toolbar strip showing symbol search, interval selector, compare, storage, indicators, chart type, range, undo, timezone, screenshot, and fullscreen controls in one row The top toolbar. Every numbered region below sits inside the single period-bar slot.

  1. Symbol quick search — inside period-bar
  2. Interval selector and time-frame shortcuts — inside period-bar
  3. Compare, storage, and indicators entries — inside period-bar
  4. Chart type and range controls — inside period-bar
  5. Undo, timezone, screenshot, and fullscreen actions — inside period-bar

A period-bar slot styles the whole strip. To show, hide, or reorder an individual control inside it, use its built-in control id (toolbar.quick-search, toolbar.interval-selector, ...) through ToolbarCustomization — see control composition.

TradeScript pane legend showing the OHLCV values row, symbol identity, and indicator rows with visibility, settings, and remove controls The pane legend. Every numbered region below sits inside the pane-header slot.

  1. Current OHLCV values and status row — inside pane-header
  2. Symbol identity (name, logo, exchange) — inside pane-header
  3. Indicator rows with per-indicator values — inside pane-header
  4. Visibility, settings, and remove controls per row — inside pane-header

A pane-header slot styles the legend surface; indicator identity, values, and the row controls keep their own semantics.

The two props

Every chart surface takes the same two props:

<TradeScriptWidget
sdk={sdk}
symbol="AAPL"
interval="1D"
datafeed={datafeed}
slotClassNames={{ 'period-bar': 'my-toolbar' }}
slotStyles={{ 'chart-shell': { borderRadius: 0 } }}
/>

A slot is a value or a function

Pass a literal when the styling is fixed:

slotClassNames={{ 'drawing-sidebar': 'border-r border-slate-700' }}

Pass a function when it depends on the chart's state. It receives the surface, the active preset, and whether the chart is fullscreen:

slotStyles={{
'chart-shell': ({ theme, fullscreen }) => ({
borderRadius: fullscreen ? 0 : 8,
outline: theme === 'light' ? '1px solid #e5e7eb' : 'none',
}),
}}

Returning undefined from a function means "no opinion" — the surface keeps its own styling. An unset slot is not the same as an empty string: an empty class would append a stray token, and an empty style object would not.

Surface by surface

chart-shell

The outer chart frame, around everything else. Style it to square corners, add an outline, or blend the chart into a host panel.

slotStyles={{
'chart-shell': ({ fullscreen }) => ({ borderRadius: fullscreen ? 0 : 8 }),
}}

Effect: the chart's outer frame gains an 8px radius in normal mode and squares off in fullscreen. Focus boundary and lifecycle are untouched.

period-bar

The top toolbar: symbol, interval, chart type, tools — the strip in the first close-up above.

slotClassNames={{ 'period-bar': 'my-toolbar' }}

Effect: my-toolbar is appended after the toolbar's own classes, so your equal-specificity rules win and hover/focus states remain available. Built-in actions, state, and responsive overflow keep working.

drawing-sidebar

The drawing-tool rail on the left edge of the chart.

slotClassNames={{ 'drawing-sidebar': 'border-r border-slate-700' }}

Effect: the rail gains a right border; tool ids and active/disabled states are untouched.

pane-header

A pane's legend row, including sub-pane headers — the second close-up above.

slotStyles={{ 'pane-header': { background: 'rgba(2, 6, 23, 0.35)' } }}

Effect: the legend surface takes a translucent tint behind values and indicator rows; the rows' visibility, settings, and remove controls keep their own semantics.

price-scale and time-scale

The vertical and horizontal axis regions. These two work differently — they are painted by the engine into the canvas, not rendered as DOM, so a class has nothing to attach to there.

Instead, the chart renders a dedicated layer over each axis region for these two slots to target. The layer covers exactly the axis bounds, and until you set something it paints nothing and captures no pointer events — an unset slot changes nothing about the chart.

That makes them right for borders, tints, gradients and overlays on the axis strip:

slotStyles={{
'price-scale': { borderLeft: '1px solid rgba(148, 163, 184, 0.3)' },
'time-scale': { background: 'linear-gradient(transparent, rgba(0,0,0,0.2))' },
}}

Effect: a hairline divider appears at the left edge of the price axis, and the time axis fades into a subtle bottom shade. Tick labels, coordinates, and axis interaction are untouched.

It does not let you restyle the tick numerals, gridlines or axis text — those are canvas draw calls. Use the theme tokens for their colours, or customization().applyStyleOverrides() for their geometry:

customization().applyStyleOverrides({
xAxis: { tickText: { color: '#94a3b8', size: 11 } },
yAxis: { axisLine: { show: true, color: 'rgba(148,163,184,0.28)' } },
})

Widget slots

The widgets take the same two props. Today there is one slot, root — the widget's outermost element:

<WatchlistTable
slotClassNames={{ root: 'rounded-none border-x-0' }}
slotStyles={{ root: ({ theme }) => ({ outline: theme === 'light' ? '1px solid #e5e7eb' : 'none' }) }}
/>

Twelve widgets accept it: the data table, workspace tabs, market depth, time and sales, session badge, agent console, order-flow heatmap, watchlist, option chain, account summary bar, terminal action dialog and the trading terminal.

MobileChart is not among them — it is a chart, so it takes the chrome slots above rather than widget slots.

Current widget-slot scope

The shared widget contract currently exposes only root. Use a widget's own typed slot or style props when you need to target headers, rows, cells, tabs, or other internal regions.

Widgets with bespoke regions already expose their own slot unions — the order ticket's 87 style slots, the account panel's tabs and rows, the level-2 ladder's 26. Those are documented on each widget's own page.

Formatters

A slot changes how a value looks. formatters changes what it says.

Every widget that shows a number formats it itself, which is right until your app formats the same numbers differently — then the chart's prices and your prices disagree on one screen. formatters hands that decision back:

<WatchlistTable
formatters={{
price: (value) => myApp.formatPrice(value),
percent: (value) => `${value.toFixed(1)}%`,
}}
/>

Six fields, all optional: number, price, quantity, currency (which also receives the currency code), percent, and time (milliseconds). An unset field keeps the widget's own locale-aware default, so overriding price alone does not oblige you to re-supply the rest.

Your function is only ever called with a finite number. Absent, NaN and infinite values render the widget's placeholder without reaching you, so (value) => value.toFixed(2) is safe to write as-is.

Widgetnumberpricequantitycurrencypercenttime
ChartDataTablenumeric columnstime column
WatchlistTablelast/bid/ask/changevolumechange %
TimeAndSalesWidgetprint priceprint sizetimestamp
OptionChainLadderGreeks and numeric custom metricslast/mark/bid/ask/strikevolume/OIIV
BrokerAccountSummaryBarcash/equity/buying power/P&LP&L %
MarketDepthWidgetformatPriceformatSize

MarketDepthWidget predates this contract and keeps its original formatPrice / formatSize props rather than breaking them.

BrokerAccountPanel is deliberately absent: it already has customFormatters, a richer per-column registry. Two formatter mechanisms on one widget would be worse than one.

Render overrides

When a class and a formatter are not enough, some widgets let you replace a repeated region outright. Each override receives that region's data and the widget's own output as defaultRow, so wrapping costs one line and does not make you reimplement selection, drag, keyboard handling or the column grid:

<WatchlistTable
renderRow={({ symbol, quote, defaultRow }) => (
<div className={quote?.halted ? 'opacity-50' : undefined}>{defaultRow}</div>
)}
/>

Ignore defaultRow and you have replaced the row entirely:

<TimeAndSalesWidget
renderPrint={({ entry, aggressor }) => (
<tr><td>{aggressor} {entry.size} @ {entry.price}</td></tr>
)}
/>
WidgetPropRegion
WatchlistTablerenderRowone quote row
MarketDepthWidgetrenderLevelone ladder row, bid and ask paired
TimeAndSalesWidgetrenderPrintone tape print
AgentConsoleWidgetrenderCapabilitiesthe expanded capability view

(chart.setOrderFlowHeatmap({ renderMode }) is not one of these — it selects a chart-layer draw style, not a replacement renderer.)

Use render overrides only for the documented regions above. Other widget regions retain their built-in rendering and interaction behavior.

Which columns, which rows, how dense

A few widgets let you choose what is shown rather than how it looks. These are ordinary props, not slots, because they change layout rather than appearance:

<ChartDataTable density="compact" />

<BrokerAccountSummaryBar metrics={['equity', 'pnl']} />

<OptionChainLadder sdk={sdk} columns={['bid', 'ask']} />
  • ChartDataTable.densitycomfortable (default, 32px rows) or compact (24px). It drives the virtualiser as well as the row height, so compact genuinely renders more rows rather than clipping them.
  • BrokerAccountSummaryBar.metrics — which of cash, equity, buyingPower, pnl to show, in your order. A metric with no data is still skipped, so listing one never forces an empty slot.
  • OptionChainLadder.columns — which of last, volume, bid, ask to show per side, in your order. It also accepts mark, openInterest, impliedVolatility, delta, gamma, theta, vega, and rho, plus declarative customer metric columns. The call side mirrors your order so both halves stay symmetric about the strike, except that bid always reads before ask on both sides — a trader scanning across should not find the two prices transposed halfway.

Customer metrics ride on each option quote and are never calculated by the widget:

Custom option-chain metric columns example
import type { OptionChainColumnSpec } from '@tradescript/pro/react/widgets/option-chain';

const columns: readonly OptionChainColumnSpec[] = [
'last',
'impliedVolatility',
'gamma',
{
kind: 'metric',
metric: 'vanna',
label: 'Vanna',
},
{
kind: 'metric',
metric: 'gex',
label: 'GEX',
renderCell: ({ value }) =>
typeof value === 'number' ? value.toLocaleString() : '-',
},
'bid',
'ask',
];

const quote = {
contract,
bid: 4.2,
ask: 4.3,
impliedVolatility: 0.274,
gamma: 0.0237,
metrics: {
vanna: 0.0114,
gex: 838919,
},
};

<OptionChainLadder sdk={sdk} columns={columns} {...props} />;

OPTION_CHAIN_BUILT_IN_COLUMNS is the runtime catalog for building a host-side column chooser. Wider configurations use one synchronized horizontal scroll surface, so call headers, strike rows, and put headers stay aligned.

Precedence

Your slot class is appended after the surface's own classes, so equal-specificity rules win. Your slot style is spread after the surface's own inline style, so it overrides it. Between the two, prefer the class — it keeps hover and focus states available, which an inline style cannot express.

Slots never change behavior. They set appearance only: a slot cannot hide a control, reorder a toolbar, or alter what an action does. Use ToolbarCustomization for that — see Feature Gates.

  • Feature Gates — hiding, showing, and composing controls rather than restyling them.
  • Themes — the token layer slots sit on top of.
  • Customization Precedence — where slots rank against themes and overrides.
  • Toolbars — the toolbar anatomy these slot keys attach to.