Skip to main content

Mobile and Responsive Styling

On a phone, small buttons are hard to hit, small text is hard to read, and users expect apps to follow their phone's light or dark setting. Each has a styling answer:

On phones and tabletsDo
Toolbar buttons too small to tapBigger targets in CSS, under @media (pointer: coarse)
Axis labels and legend too smallA theme variant with larger text
Light or dark to match the deviceFollow prefers-color-scheme
Pane controls that need a mouse to appearThe touch layout, paneControls: { touch: true }

Touch-Sized Controls​

Aim for targets about 44 px across. Put the rules in your stylesheet under @media (pointer: coarse), which matches when the main pointer is a finger:

@media (pointer: coarse) {
.st-toggle-group__button {
min-width: 44px;
min-height: 40px;
font-size: 15px;
}
.st-candle-type-selector {
min-height: 40px;
}
.st-drawing-toolbar__button {
width: 44px;
height: 44px;
}
}

The pane control bars and dividers have their own touch layout, with a drawer and a wider grip: turn it on with paneControls: { touch: true }; see Mobile and Desktop Modes.

Larger Text on the Chart​

The chart's text is drawn by WebGL, so CSS can't size it. Make a theme variant with larger axis labels and legend, and use it on phones:

const phoneTheme = (theme: ISciTraderTheme) =>
createSciTraderTheme(theme, { chart: { axisLabelFontSize: 15, legendFontSize: 15 } });

const touch = matchMedia("(pointer: coarse)").matches;
const theme = touch ? phoneTheme(SCITRADER_DARK_THEME) : SCITRADER_DARK_THEME;

When you switch to a theme with a different legendFontSize on a live chart, also call the legend's setLegendFontSize: see Styling the Chart.

Following the Device's Light or Dark Setting​

(prefers-color-scheme: dark) matches when the device is in dark mode. Pick the theme from it, and follow it when it changes, e.g. at sunset:

const prefersDark = matchMedia("(prefers-color-scheme: dark)");
const systemTheme = () => (prefersDark.matches ? SCITRADER_DARK_THEME : SCITRADER_LIGHT_THEME);

financeChartPanel.financeApi.setTheme(systemTheme());
prefersDark.addEventListener("change", () => financeChartPanel.financeApi.setTheme(systemTheme()));

Offer "System" as one choice beside Dark and Light, as the example does, so users can still pick one.

TypeScript Example: Phone Sizes and the Device’s Dark Mode​

System follows your device’s light or dark setting; Dark and Light override it. Phone switches to larger axis labels and legend, from a theme variant, and touch-sized toolbar buttons, from CSS. In an app the CSS goes under @media (pointer: coarse); here a class turns it on, so you can see it with a mouse.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import {
applyThemeCssVariables,
CandleInterval,
CandleTypeSelector,
CoinbaseExchange,
createSciTraderTheme,
defaultExchangeProvider,
FinChartLegendModifier,
idbPriceStore,
IntervalSelector,
ISciTraderTheme,
OHLCStudy,
RSIStudy,
SciFinanceChartPanel,
SCITRADER_DARK_THEME,
SCITRADER_LIGHT_THEME,
VolumeStudy,
} from "scichart-scitrader";

// This example expects this HTML:
// <div id="app" style="display: flex; flex-direction: column; height: 100%">
// <div id="options"></div>
// <div id="toolbar"></div>
// <div id="chart" style="flex: 1; min-height: 0"></div>
// </div>

// Register a data source, once, at app startup
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);

// 1. Touch-sized controls, in CSS. In an app, put these rules in your stylesheet inside
// `@media (pointer: coarse) { … }`, so they apply on touch screens. Here, the "Phone" button adds a
// class instead, so you can see them with a mouse
document.head.appendChild(document.createElement("style")).textContent = `
.phone .st-toggle-group__button { min-width: 44px; min-height: 40px; font-size: 15px; }
.phone .st-candle-type-selector { min-height: 40px; font-size: 15px; }
`;

// 2. Larger text on the chart: a theme variant with bigger axis labels and legend
const phoneVariant = (theme: ISciTraderTheme) =>
createSciTraderTheme(theme, { chart: { axisLabelFontSize: 15, legendFontSize: 15 } });

// 3. Follow the device's light or dark setting
const prefersDark = matchMedia("(prefers-color-scheme: dark)");

type TScheme = "system" | "dark" | "light";
let scheme: TScheme = "system";
let phone = false;

async function createChart() {
const financeChartPanel = await SciFinanceChartPanel.create(document.getElementById("chart"), {
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "BTC-USD",
interval: CandleInterval.ONE_HOUR,
period: "1W",
theme: currentTheme(),
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy()],
});
const chart = financeChartPanel.financeApi;

const toolbar = document.getElementById("toolbar");
Object.assign(toolbar.style, barStyle);
IntervalSelector.create(toolbar, {
value: CandleInterval.ONE_HOUR,
intervals: [CandleInterval.FIVE_MINUTES, CandleInterval.ONE_HOUR, CandleInterval.SIX_HOURS, CandleInterval.ONE_DAY],
onChange: (interval) => chart.changeInterval(interval),
});
CandleTypeSelector.create(toolbar, { value: chart.getCandleType(), onChange: (type) => chart.setCandleType(type) });

function update() {
const theme = currentTheme();
chart.setTheme(theme);
// The legend doesn't redraw at a new legendFontSize on setTheme yet, so set it on the legend too
chart.finChartModifiers
.asArray()
.find((m): m is FinChartLegendModifier => m instanceof FinChartLegendModifier)
?.setLegendFontSize(theme.chart.legendFontSize);
applyThemeCssVariables(document.body, theme);
document.getElementById("app").classList.toggle("phone", phone);
renderOptions();
}

function renderOptions() {
const bar = document.getElementById("options");
Object.assign(bar.style, barStyle);
const option = (label: string, selected: boolean, onClick: () => void) => {
const button = document.createElement("button");
button.textContent = label;
Object.assign(button.style, buttonStyle(selected));
button.onclick = () => {
onClick();
update();
};
return button;
};
bar.replaceChildren(
option(`System (${prefersDark.matches ? "dark" : "light"})`, scheme === "system", () => (scheme = "system")),
option("Dark", scheme === "dark", () => (scheme = "dark")),
option("Light", scheme === "light", () => (scheme = "light")),
option("Desktop", !phone, () => (phone = false)),
option("Phone", phone, () => (phone = true))
);
}

// When the device switches between light and dark, follow it
prefersDark.addEventListener("change", () => scheme === "system" && update());
update();

// When the page goes away: financeChartPanel.cleanup();
return financeChartPanel;
}

function currentTheme(): ISciTraderTheme {
const dark = scheme === "dark" || (scheme === "system" && prefersDark.matches);
const base = dark ? SCITRADER_DARK_THEME : SCITRADER_LIGHT_THEME;
return phone ? phoneVariant(base) : base;
}

// The bars' colours are the theme's CSS variables, so they follow the theme too
const barStyle: Partial<CSSStyleDeclaration> = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: "6px",
padding: "6px 12px",
background: "var(--st-ui-toolbar-background)",
borderBottom: "1px solid var(--st-ui-divider)",
};

const buttonStyle = (selected: boolean): Partial<CSSStyleDeclaration> => ({
padding: "4px 12px",
border: "1px solid var(--st-ui-divider)",
borderRadius: "4px",
background: selected ? "var(--st-ui-accent)" : "var(--st-ui-button-background)",
color: selected ? "#ffffff" : "var(--st-ui-toolbar-text)",
font: "13px var(--st-font-family, sans-serif)",
cursor: "pointer",
});

createChart();

React Example: Phone Sizes and the Device’s Dark Mode​

The same in React. A useSyncExternalStore hook follows prefers-color-scheme.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import * as React from "react";
import {
CandleInterval,
CoinbaseExchange,
createSciTraderTheme,
defaultExchangeProvider,
ECandleType,
FinChartLegendModifier,
idbPriceStore,
ISciFinanceChartPanelApi,
ISciFinanceChartPanelConfig,
ISciTraderTheme,
OHLCStudy,
RSIStudy,
SCITRADER_DARK_THEME,
SCITRADER_LIGHT_THEME,
VolumeStudy,
} from "scichart-scitrader";
import {
CandleTypeSelector,
IntervalSelector,
SciFinanceChartView,
SciTraderThemeProvider,
} from "scichart-scitrader-react";

// Register a data source, once, at app startup
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);

// 1. Touch-sized controls, in CSS. In an app, put these rules in your stylesheet inside
// `@media (pointer: coarse) { … }`, so they apply on touch screens. Here, the "Phone" button adds a
// class instead, so you can see them with a mouse
const TOUCH_CSS = `
.phone .st-toggle-group__button { min-width: 44px; min-height: 40px; font-size: 15px; }
.phone .st-candle-type-selector { min-height: 40px; font-size: 15px; }
`;

// 2. Larger text on the chart: a theme variant with bigger axis labels and legend
const phoneVariant = (theme: ISciTraderTheme) =>
createSciTraderTheme(theme, { chart: { axisLabelFontSize: 15, legendFontSize: 15 } });

// 3. Follow the device's light or dark setting
const prefersDark = matchMedia("(prefers-color-scheme: dark)");
function usePrefersDark(): boolean {
return React.useSyncExternalStore(
(onChange) => {
prefersDark.addEventListener("change", onChange);
return () => prefersDark.removeEventListener("change", onChange);
},
() => prefersDark.matches,
() => true // server render: assume dark
);
}

const INTERVALS = [CandleInterval.FIVE_MINUTES, CandleInterval.ONE_HOUR, CandleInterval.SIX_HOURS, CandleInterval.ONE_DAY];
type TScheme = "system" | "dark" | "light";

export function ResponsiveChart() {
const systemDark = usePrefersDark();
const [scheme, setScheme] = React.useState<TScheme>("system");
const [phone, setPhone] = React.useState(false);
const dark = scheme === "dark" || (scheme === "system" && systemDark);
const theme = React.useMemo(() => {
const base = dark ? SCITRADER_DARK_THEME : SCITRADER_LIGHT_THEME;
return phone ? phoneVariant(base) : base;
}, [dark, phone]);

const [api, setApi] = React.useState<ISciFinanceChartPanelApi>();
const [interval, setInterval] = React.useState(CandleInterval.ONE_HOUR);
const [candleType, setCandleType] = React.useState(ECandleType.Candlestick);

// The chart takes the theme through setTheme
React.useEffect(() => {
if (!api) return;
const chart = api.financeApi;
chart.setTheme(theme);
// The legend doesn't redraw at a new legendFontSize on setTheme yet, so set it on the legend too
chart.finChartModifiers
.asArray()
.find((m): m is FinChartLegendModifier => m instanceof FinChartLegendModifier)
?.setLegendFontSize(theme.chart.legendFontSize);
}, [api, theme]);

// Create the config once per mounted chart: studies are live objects bound to one chart
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "BTC-USD",
interval: CandleInterval.ONE_HOUR,
period: "1W",
theme,
studies: [new OHLCStudy(), new VolumeStudy(), new RSIStudy()],
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);

const option = (label: string, selected: boolean, onClick: () => void) => (
<button key={label} style={buttonStyle(selected)} onClick={onClick}>
{label}
</button>
);

return (
<SciTraderThemeProvider theme={theme}>
<style>{TOUCH_CSS}</style>
<div className={phone ? "phone" : undefined} style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<div style={barStyle}>
{option(`System (${systemDark ? "dark" : "light"})`, scheme === "system", () => setScheme("system"))}
{option("Dark", scheme === "dark", () => setScheme("dark"))}
{option("Light", scheme === "light", () => setScheme("light"))}
{option("Desktop", !phone, () => setPhone(false))}
{option("Phone", phone, () => setPhone(true))}
</div>
<div style={barStyle}>
<IntervalSelector
value={interval}
intervals={INTERVALS}
onChange={(next) => {
setInterval(next);
api?.financeApi.changeInterval(next);
}}
/>
<CandleTypeSelector
value={candleType}
onChange={(type) => {
setCandleType(type);
api?.financeApi.setCandleType(type);
}}
/>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<SciFinanceChartView config={config} onInit={setApi} />
</div>
</div>
</SciTraderThemeProvider>
);
}

// The bars' colours are the theme's CSS variables, so they follow the theme too
const barStyle: React.CSSProperties = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: 6,
padding: "6px 12px",
background: "var(--st-ui-toolbar-background)",
borderBottom: "1px solid var(--st-ui-divider)",
};

const buttonStyle = (selected: boolean): React.CSSProperties => ({
padding: "4px 12px",
border: "1px solid var(--st-ui-divider)",
borderRadius: 4,
background: selected ? "var(--st-ui-accent)" : "var(--st-ui-button-background)",
color: selected ? "#ffffff" : "var(--st-ui-toolbar-text)",
font: "13px var(--st-font-family, sans-serif)",
cursor: "pointer",
});