DocumentationKeyboard Shortcuts

Keyboard Shortcuts

The chart ships a small, framework-free keyboard system: a default keymap wired to real chart actions, layout-independent combos, live rebinding, an alternate preset, hover-vs-global scope, custom commands, and optional persistence. It is on by default - pass shortcuts: false to createChart to turn it off.

Try it

Hover (or click) this chart and use the keys:

live
Rendering live chart…
Arrows pan · = / - zoom · Home or 0 reset · Alt+V / Alt+H toggle grids · Alt+M crosshair magnet · Alt+Shift+S screenshot.
View example code
function stockBars(startTime, count, intervalSec, startPrice, seed, volatility = 0.004, baseVolume = 2000) {
  let state = seed >>> 0 || 1;
  const random = () => {
    state ^= state << 13; state >>>= 0;
    state ^= state >>> 17;
    state ^= state << 5; state >>>= 0;
    return state / 4294967296;
  };
  const round = value => Math.round(value * 100) / 100;
  const bars = [];
  const trends = [0.5, -0.35, 0.2, 0.85, -0.7, 0.15, -0.4];
  let close = round(startPrice);
  let regimeEnd = 0;
  let trend = 0;
  for (let i = 0; i < count; i++) {
    if (i >= regimeEnd) {
      trend = trends[Math.floor(random() * trends.length)] * volatility;
      regimeEnd = i + 14 + Math.floor(random() * 18);
    }
    const gap = i > 0 && i % 37 === 0 ? (random() - 0.5) * volatility * 6 : 0;
    const open = round(Math.max(0.01, close * (1 + gap)));
    const noise = (random() + random() + random() - 1.5) * volatility * 2;
    const move = trend + noise;
    close = round(Math.max(0.01, open * (1 + move)));
    const upperWick = Math.max(0.01, round(open * volatility * (0.12 + random() * 0.7)));
    const lowerWick = Math.max(0.01, round(open * volatility * (0.12 + random() * 0.7)));
    const volume = Math.round(baseVolume * (0.65 + random() * 0.8
      + Math.abs(move) / volatility * 0.18 + (i % 31 === 0 ? 1.5 : 0)));
    bars.push({ time: startTime + i * intervalSec, open,
      high: round(Math.max(open, close) + upperWick),
      low: round(Math.max(0.01, Math.min(open, close) - lowerWick)),
      close, volume });
  }
  return bars;
}
const chart = lib.createChart(el);
chart.addSeries('candlestick').setData(stockBars(1700000000, 160, 3600, 1480, 11, 0.004));
chart.timeScale.fitContent(160);
return chart;

Default keymap

Mod is the platform accelerator: Ctrl on Windows/Linux, ⌘ on macOS.

CommandActionDefault
panLeft / panRightPan two barsArrowLeft / ArrowRight
panLeftFast / panRightFastPan ten barsMod + ArrowLeft / Mod + ArrowRight
panLeftBar / panRightBarStep one bar (time navigator buttons)unbound
panUp / panDownPan priceArrowUp / ArrowDown
zoomIn / zoomOutZoom= / -
resetScaleFit + re-enable autoscaleHome / 0
fitContentFit all barsAlt + F
screenshotDownload a PNGAlt + Shift + S
toggleGridVert / toggleGridHorzToggle grid linesAlt + V / Alt + H
toggleCrosshairMagnetFree vs magnet crosshairAlt + M

Combo format

Combos are strings of zero or more modifiers then one key, joined with +, using physical key codes so bindings survive keyboard layouts.

  • Modifiers, in this order: Mod, Ctrl, Meta, Alt, Shift.
  • Keys are codes: KeyA-KeyZ, Digit0-Digit9, ArrowLeft/Right/Up/Down, Home, Equal, Minus, Space, Escape, F1-F12, …

Examples: 'Alt+KeyR', 'Mod+ArrowLeft', 'Shift+Digit0'.

Configure at creation

import { createChart } from 'openalgo-charts';
 
const chart = createChart(el, {
  shortcuts: {
    preset: 'default',          // or 'alt'
    scope: 'hover',             // or 'global'
    overrides: {
      panLeftFast: 'Alt+ArrowLeft', // rebind
      screenshot: null,             // unbind (still listed)
    },
    disabledCommands: ['toggleCrosshairMagnet'],
    customShortcuts: [
      { command: 'openOrders', label: 'Open orders', combos: 'Alt+KeyO', onTrigger: () => openOrders() },
    ],
    persist: true,              // save rebinds to localStorage
  },
});

Rebind live

chart.shortcuts is the manager (or null when disabled):

chart.shortcuts?.setBinding('zoomIn', 'Shift+Equal'); // returns false for reserved combos
chart.shortcuts?.disable('panUp');
chart.shortcuts?.resetBinding('panUp');
chart.shortcuts?.setPreset('alt');
chart.shortcuts?.resetAll();
 
chart.shortcuts?.list();   // [{ command, label, combos, isCustom, isDisabled }]
chart.shortcuts?.state();  // { preset, overrides, disabled } - serializable
chart.shortcuts?.onChange(() => refresh()); // since 2.5.10: after every change; returns an unsubscribe

The time navigator’s key hints follow a rebind (since 2.5.10), unless the host passed its own timeNavigator.hints.

Browser/OS-reserved combos (Mod+KeyW, Mod+KeyT, Mod+KeyR, …) can’t be bound - setBinding returns false and leaves the command unchanged. Check with isReservedCombo.

Alt preset

preset: 'alt' (or chart.shortcuts.setPreset('alt')) is an overlay on the defaults - only the combos with a different convention change:

CommandAlt preset
panLeftFast / panRightFastAlt + ArrowLeft / Alt + ArrowRight
screenshotMod + Shift + S

User rebinds always win over the active preset.

Scope

  • hover (default) - shortcuts fire only while the pointer is over the chart, or while it is focused. On a multi-chart page this routes each keystroke to one chart.
  • global - always active (for chart-centric, single-chart pages).

Keys are always ignored while you type in an input, textarea, select, or any contenteditable element.

Chart grid

A chart grid adds its own chords to each chart’s keymap, in a Chart grid section of the shortcuts panel. Only the active chart answers them. They name no command, so the shortcuts editor lists them without a Change button.

KeysAction
Alt + EnterMaximize or restore the active chart
EscapeRestore the grid, when nothing else on the chart wants the key
Alt + Shift + ArrowMake the chart in that direction active
Mod + Shift + ArrowSwap the active chart with the chart in that direction

See Chart grid.

Custom shortcuts

Add your own commands - they join the keymap and fire your handler:

createChart(el, {
  shortcuts: {
    customShortcuts: [
      { command: 'toggleHeatmap', label: 'Toggle heatmap', combos: 'Alt+KeyG', onTrigger: () => toggleHeatmap() },
    ],
  },
});

A matched custom command runs its onTrigger, consumes the key, and emits the same trigger event as a built-in.

Persistence

With persist: true, rebinds / preset / disabled commands round-trip through localStorage (key openalgo-charts:shortcuts, override with storageKey). It is guarded for non-browser environments, so SSR and tests are safe.

Helpers

Exported for building your own shortcut UI:

import {
  parseCombo, normalizeCombo, formatCombo, isValidCombo, isReservedCombo, eventToCombo,
} from 'openalgo-charts';
 
formatCombo('Alt+KeyR');            // 'Alt + R'  (or '⌥ R' on macOS)
normalizeCombo('Shift+Mod+KeyA');   // 'Mod+Shift+KeyA'
eventToCombo(keyboardEvent);        // 'Mod+ArrowLeft'

Events

Subscribe to every trigger (built-in or custom):

const off = chart.shortcuts?.on((e) => {
  // e = { command, combo, isCustom }
  analytics.track('shortcut', e.command);
});
// off?.() to unsubscribe

The widget’s shortcuts editor (since 2.5.10)

openalgo-charts/widget puts every chord it answers to in one keymap: its own editing keys and tool chords, the rail’s keys, and the chart commands above. Press ? over a widget and the shortcuts panel lists them all, and is also where a user changes them:

  • Change on a row records the next chord pressed. Escape cancels and leaves the panel open; Tab ends the recording and moves on.
  • A chord another binding holds is not taken silently. The row names who has it and offers Replace, which moves the chord and leaves the other binding without one (its row then reads Not set), or Cancel.
  • A chord the browser keeps (Ctrl+W, Ctrl+T and the rest) is refused, and so is a letter or digit without Ctrl or Alt, because on its own it starts typing a symbol or an interval on the chart, and so is Space on its own, which presses the focused button. The panel says why and keeps listening.
  • Reset on a changed row puts its default back; Reset all, after a confirmation, puts every default back.

Chart commands are rebound through chart.shortcuts with the physical key, in the engine’s code grammar above, so they survive a keyboard layout. The widget’s own commands keep the key grammar they were written in (Alt+T, Mod+Z). Changes apply at once. With persist on, they are saved in the widget’s storage under the key keymap (KEYMAP_KEY) and put back when the widget mounts again.

A chart command resets to the chord it had when the widget mounted, so bindings a host passes in shortcuts.overrides count as its defaults. The widget saves the chart’s chords itself: leave the engine’s own shortcuts.persist off on a widget, or a chord the user chose in an earlier visit is read back as the default and Reset has nothing to return to.

live
Rendering live chart…
Hover the chart and press ?. Change a row, press a chord, and try Alt+H on the trend line row to see a conflict. Alt+Y already picks the trend line: the example moved it.
View example code
function stockBars(startTime, count, intervalSec, startPrice, seed, volatility = 0.004, baseVolume = 2000) {
  let state = seed >>> 0 || 1;
  const random = () => {
    state ^= state << 13; state >>>= 0;
    state ^= state >>> 17;
    state ^= state << 5; state >>>= 0;
    return state / 4294967296;
  };
  const round = value => Math.round(value * 100) / 100;
  const bars = [];
  const trends = [0.5, -0.35, 0.2, 0.85, -0.7, 0.15, -0.4];
  let close = round(startPrice);
  let regimeEnd = 0;
  let trend = 0;
  for (let i = 0; i < count; i++) {
    if (i >= regimeEnd) {
      trend = trends[Math.floor(random() * trends.length)] * volatility;
      regimeEnd = i + 14 + Math.floor(random() * 18);
    }
    const gap = i > 0 && i % 37 === 0 ? (random() - 0.5) * volatility * 6 : 0;
    const open = round(Math.max(0.01, close * (1 + gap)));
    const noise = (random() + random() + random() - 1.5) * volatility * 2;
    const move = trend + noise;
    close = round(Math.max(0.01, open * (1 + move)));
    const upperWick = Math.max(0.01, round(open * volatility * (0.12 + random() * 0.7)));
    const lowerWick = Math.max(0.01, round(open * volatility * (0.12 + random() * 0.7)));
    const volume = Math.round(baseVolume * (0.65 + random() * 0.8
      + Math.abs(move) / volatility * 0.18 + (i % 31 === 0 ? 1.5 : 0)));
    bars.push({ time: startTime + i * intervalSec, open,
      high: round(Math.max(open, close) + upperWick),
      low: round(Math.max(0.01, Math.min(open, close) - lowerWick)),
      close, volume });
  }
  return bars;
}
const widget = lib.createWidget(el, {
symbol: 'DEMO',
exchange: 'NSE',
interval: '5m',
intervals: ['1m', '5m', '15m', '1h'],
});
widget.series.setData(stockBars(1700000000, 240, 300, 1480, 23, 0.003));
widget.chart.timeScale.fitContent(160);

// The same move the panel makes: the trend line answers to Alt+Y now.
widget.context.keymap.rebind('tool:trend-line', 'Alt+Y');
return widget;

Commands

Every binding a user may move has a stable command name, which is what rebind, reset and a saved record refer to: the chord is exactly what changes.

CommandDefaultRebindable
undo, redoMod + Z, Mod + Shift + Zyes
redo-alt (not listed)Mod + Yyes
copy, cut, paste, duplicateMod + C, Mod + X, Mod + V, Mod + Dyes
deleteDeleteyes
tool:<id>, one per drawing tool with a chordAlt + T, Alt + H, Alt + J, Alt + V, Alt + Cyes
chart:<command>, every command in the default keymapas aboveyes
delete-back, finish, leaveBackspace, Enter, Escapeno
nudge-left, nudge-right, nudge-up, nudge-downthe arrowsno
shortcuts?no, it is the way into the editor

The rail’s arrow keys and the drawing toolbar’s keys have no command and stay where they are.

From code

widget.context.keymap is the widget’s Keymap:

const km = widget.context.keymap;
 
km.rebind('tool:trend-line', 'Alt+Y');       // { ok: true, conflicts: [] }
km.rebind('tool:trend-line', 'Alt+H');       // { ok: false, reason: 'taken', conflicts: [...] }
km.rebind('tool:trend-line', 'Alt+H', { replace: true }); // the horizontal line gives it up
km.rebind('chart:fitContent', 'Alt+KeyG');   // an engine command, in its code grammar
km.rebind('copy', null);                     // unbound
 
km.chord('undo');                            // 'Mod+z', or '' while unbound
km.conflictsFor('Alt+O');                    // who holds a chord, before taking it
km.reset('tool:trend-line');                 // may report 'taken' too; pass { replace: true }
km.resetAll();
 
const saved = km.overrides();                // { 'tool:trend-line': 'Alt+h', 'chart:fitContent': ['Alt+KeyG'] }
km.applyOverrides(saved);                    // replaces rather than merges
const off = km.onChange(({ commands }) => console.log('moved', commands));

rebind refuses with a reason: unknown (no such command), fixed (not rebindable), invalid (not a chord), reserved (the browser keeps it) or taken, with the holders in conflicts. applyOverrides reads anything a store hands back: an entry it cannot use is dropped, and one for a command nothing has registered yet is kept for when it does.

A host’s own command joins the panel with a Change button once it has a command:

km.register('Alt+O', () => openOrders(), 'widget', {
  command: 'orders', label: 'Open orders', group: 'Trading',
});

For a host that does not want its users changing chords, createWidget(el, { shortcutsEditor: false }) makes the ? panel a plain list and applies no saved chords. openShortcutsPanel(widget.context) opens the same panel ? does from a control of the host’s own, and { edit: false } asks for the plain list whatever the widget allows.