Customization
The toolbar and your buttons
Hide what you do not want, add your own buttons and menus, and keep it accessible.
The Toolbar Is a Set of Switches
createChartShell draws a toolbar above the chart and a drawing rail beside it. Every item
is independently switchable, and everything an item does is also a method on the returned
shell, so a host can hide the whole toolbar and still drive the chart from its own controls.
import { createChartShell } from "@ortex-charts/ui";
const shell = createChartShell(el, {
symbol: "AAPL",
datafeed,
toolbar: { theme: false, screenshot: false, download: true },
resolutions: ["5", "15", "60", "1D", "1W"],
indicators: ["sma", "ema", "rsi", "macd", "bb"],
drawingTools: ["trendline", "ray", "horizontalLine", "rectangle", "fibRetracement"],
});@ortex-charts/uiThe Built-In Items
ToolbarItem is the union of the switch names. Everything is on by default except
download.
| Item | What it is | Shell method |
|---|---|---|
symbol |
Symbol button opening a search popover; Enter loads typed text | setSymbol |
resolution |
Segmented buttons labeled 1m 5m 15m 1h D W M |
setResolution |
seriesType |
Candles, bars, line, area, baseline, plus Heikin-Ashi and hollow toggles | setSeriesType |
indicators |
Searchable list of the registry, plus the active list with settings, hide and remove | addIndicator, indicators |
overlays |
Host data overlays, as a menu or the "Add data to chart" modal | setOverlay, overlays, openDataPicker |
scale |
Auto, logarithmic and percent scale modes | setScaleMode |
fit |
Fit the whole history into view | — |
realtime |
Scroll to the latest bar | — |
replay |
Bar replay; off by default | replay, startReplay, stopReplay |
screenshot |
PNG download, or onScreenshot |
— |
download |
CSV of the visible series; off by default | exportCsv |
theme |
Light and dark toggle | setTheme |
fullscreen |
Full screen and back | toggleFullscreen |
toolbar: { replay: true, download: true, theme: false }
Switching everything off leaves a bare chart that is still fully driven by the shell methods, which is the supported way to build a completely custom interface without giving up symbol binding, layouts and overlays.
drawings: false removes the drawing rail entirely. indicators: false removes the
indicator menu. indicators: ["sma", "ema"] limits which ones the menu offers without
affecting what addIndicator can do from code.
Configuration as Data
Every switch is plain JSON, so a company's chart configuration can live in a checked-in file with only the callbacks attached in code.
{
"resolutions": ["5", "15", "60", "1D", "1W"],
"toolbar": { "theme": false, "screenshot": false, "download": true },
"indicators": ["sma", "ema", "rsi", "macd", "bb"],
"drawingTools": ["trendline", "ray", "horizontalLine", "rectangle", "fibRetracement"],
"dataPicker": "modal",
"compare": true
}
import config from "./chart-config.json";
createChartShell(el, {
...config,
datafeed,
overlays,
onLayoutChange: (layout) => api.saveChartLayout("stock-page", layout),
});
Your Own Buttons
ToolbarAction adds a button or a dropdown. It is data plus callbacks, so it fits the same
configuration-file pattern.
interface ToolbarAction {
id: string;
label?: string; // icon-only when omitted
icon?: IconName | string; // a built-in name, or your own <svg …> markup
title?: string; // tooltip and accessible name; defaults to label
placement?: "start" | "middle" | "end";
onClick?: (shell: ChartShell, event: MouseEvent) => void;
menu?: Array<{ label: string; icon?: IconName; hint?: string; onSelect: (shell: ChartShell) => void }>;
active?: boolean; // render as pressed
disabled?: boolean;
}
createChartShell(el, {
symbol: "AAPL",
datafeed,
actions: [
{
id: "api",
label: "Download with API",
icon: "api",
placement: "end",
onClick: (shell) => openApiDialog(shell.symbol(), shell.resolution()),
},
{
id: "share",
icon: "share",
title: "Share",
menu: [
{ label: "Copy link", onSelect: (s) => navigator.clipboard.writeText(linkFor(s.getLayout())) },
{ label: "Post to Slack", icon: "camera", onSelect: (s) => slack(s.chart.chart.takeScreenshot()) },
],
},
],
});
placement decides where the button lands: start puts it with the symbol and resolutions,
middle with the chart-type menus, and end at the right, which is the default.
At runtime, shell.addAction(action) adds one and returns a function that removes it again,
which is how a button appears only in a particular application state.
const removeAlertButton = shell.addAction({
id: "add-alert",
icon: "plus",
title: "Add alert at the last close",
onClick: (s) => {
const last = s.chart.main.lastIndex();
const v = s.chart.main.valueAt(last);
if (v && "close" in v) s.chart.alerts().add({ price: v.close, label: "New alert" });
},
});
removeAlertButton();
Icons
ICONS is a record of 52 inline SVG path fragments and icon(name) builds an <svg>
element from one. They are 20-by-20, single stroke, and use currentColor, so they inherit
the button color and follow the theme.
import { ICONS, icon } from "@ortex-charts/ui";
Object.keys(ICONS); // every available IconName
const el = icon("magnet");
The names, grouped by what they are for:
- Series types:
candles,bars,line,area,baseline,heikinAshi,hollow. - Tools:
cursor,crosshair,trendline,ray,horizontalLine,verticalLine,rectangle,parallelChannel,fibRetracement,measure,text,magnet. - Editing:
undo,redo,trash,clear,eye,eyeOff,settings,lock,plus,minus,check,close,chevron. - Navigation and scale:
fit,realtime,log,percent,auto,search. - Replay:
replay,play,pause,stepForward,stepBack,stop. - Actions:
camera,download,share,link,api,indicator,sun,moon.
Passing your own markup instead of a name works and is the right answer for a brand icon:
{ id: "brand", icon: '<path d="M4 16 L10 6 L16 16 Z" fill="none" stroke="currentColor" stroke-width="1.6"/>' }
Use a 20-by-20 or 24-by-24 coordinate space and currentColor strokes so the icon matches
the built-ins at every size and in both themes.
Menus and Dialogs
The same primitives the built-in toolbar uses are exported, so a host action can open a menu
or a form that looks exactly like the rest of the chrome rather than like a bolted-on
window.prompt.
import { openMenu, openDialog, openSymbolSearch } from "@ortex-charts/ui";
openMenu(shell.root, anchorButton, (menu) => {
menu.head("Export");
menu.item({ label: "CSV", icon: "download", onSelect: () => save(shell.exportCsv()) });
menu.item({ label: "PNG", icon: "camera", onSelect: () => savePng(shell.chart.chart.takeScreenshot()) });
menu.sep();
menu.item({ label: "Include hidden series", checked: includeHidden, onSelect: () => { includeHidden = !includeHidden; return false; } });
});
Returning false from onSelect keeps the menu open, which is what a toggle wants.
openDialog(shell.root, {
title: "Alert settings",
fields: [
{ key: "price", label: "Price", type: "number", value: 182.5, step: 0.01 },
{ key: "repeat", label: "Repeat", type: "boolean", value: false },
{ key: "channel", label: "Notify by", type: "select", value: "email", options: ["email", "push", "none"] },
],
submitLabel: "Create",
onSubmit: (values) => createAlert(values),
});
openDialog returns a function that closes it. Fields are number, select or boolean;
this is the same generator the indicator settings dialog uses, which is why an indicator you
register yourself gets a proper form without writing one.
Keyboard and Accessibility
What is implemented:
- The chart is focusable. Arrow keys scroll, with Shift for a larger step;
+and-zoom about the center; Home resets the time scale. - Drawings respond to keys on a focused chart: Escape cancels a tool or clears the selection, Delete and Backspace remove the selected drawing, and Ctrl or Cmd with Z and Y undo and redo. See Drawing tools.
- Toolbar buttons carry
aria-pressedso a screen reader announces the active chart type, scale mode and tool. - Menus are
role="menu"witharia-haspopupandaria-expandedon the button that opens them, arrow-key navigation between items,aria-checkedon toggles, Escape to close, and focus returned to the anchor. - Dialogs are
role="dialog"witharia-modal, an accessible name from the title, a focus trap, Enter to submit and Escape to cancel. - Icon-only buttons carry a
titleand an accessible name, which is whyToolbarActionfalls back fromtitletolabel. - Reduced motion is respected in the chrome CSS.
What is not implemented, stated plainly:
- There is no screen-reader description of the data. The canvas is not annotated, and there is no textual summary of the series, the visible range or the values at the crosshair.
- There is no focusable data table as an alternative representation, which is the approach the Highcharts accessibility module takes.
- There is no high-contrast theme shipped, though one is a theme object away.
- Keyboard support does not extend to creating or editing drawings, only to canceling and deleting them.
If accessibility conformance is a procurement requirement for you, these are the gaps to weigh, and the CSV export plus your own table is the honest interim answer.
Mobile
Below 640 pixels the toolbar drops its labels, the resolution strip scrolls horizontally,
and the drawing rail becomes a horizontal bar under the chart. Coarse pointers get 32-pixel
targets. Pinch zoom, drag panning and long-press crosshair are in the engine rather than in
the chrome, so they work on a bare createFinancialChart too.
Full screen uses the Fullscreen API on the shell root, and falls back to a fixed-position layer where the API is missing, which is the case on iOS Safari. Escape leaves either.
Driving the Chart Without the Toolbar
Every method behind the buttons is public, so switching the toolbar off is not a loss of functionality.
const BARE = {
symbol: false, resolution: false, seriesType: false, indicators: false, overlays: false,
scale: false, fit: false, realtime: false, screenshot: false, theme: false, fullscreen: false,
} as const;
const shell = createChartShell(el, { symbol: "AAPL", datafeed, toolbar: BARE, drawings: false });
await shell.setSymbol("MSFT");
await shell.setResolution("60");
shell.setSeriesType("line");
shell.setScaleMode("logarithmic");
shell.addIndicator("rsi", { length: 21 });
await shell.setOverlay("si", true);
shell.setTheme("light");
await shell.toggleFullscreen();
const csv = shell.exportCsv();
const layout = shell.getLayout();
See the shell and layouts for the full surface, including overlays, layouts and per-series placement.