Skip to content

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"],
});
Your buttons, your toolbar@ortex-charts/ui
Loading your buttons, your toolbar
Hide the built-in buttons you do not want and add your own, with icons, menus and keyboard access.

Open this example with its source

The 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: '&#x3C;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.

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-pressed so a screen reader announces the active chart type, scale mode and tool.
  • Menus are role="menu" with aria-haspopup and aria-expanded on the button that opens them, arrow-key navigation between items, aria-checked on toggles, Escape to close, and focus returned to the anchor.
  • Dialogs are role="dialog" with aria-modal, an accessible name from the title, a focus trap, Enter to submit and Escape to cancel.
  • Icon-only buttons carry a title and an accessible name, which is why ToolbarAction falls back from title to label.
  • 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.