Styling, themes and dark mode
Register a theme object, switch it at runtime, follow the operating system preference, and override design tokens without rebuilding the whole option.
Registering a theme
import * as echarts from 'echarts/core';
const brandTheme = {
color: ['#6d28d9', '#0ea5e9', '#f59e0b', '#10b981', '#ef4444', '#64748b'],
backgroundColor: 'transparent',
textStyle: { fontFamily: 'Inter, system-ui, sans-serif', fontSize: 12 },
title: {
textStyle: { color: '#0f172a', fontWeight: 600, fontSize: 15 },
subtextStyle: { color: '#64748b' }
},
legend: { textStyle: { color: '#334155' } },
tooltip: {
backgroundColor: 'rgba(15, 23, 42, 0.92)',
borderWidth: 0,
textStyle: { color: '#f8fafc' }
},
categoryAxis: {
axisLine: { lineStyle: { color: '#cbd5e1' } },
axisLabel: { color: '#475569' },
splitLine: { show: false }
},
valueAxis: {
axisLine: { show: false },
axisLabel: { color: '#475569' },
splitLine: { lineStyle: { color: '#e2e8f0', type: 'dashed' } }
}
};
echarts.registerTheme('brand', brandTheme);
// Apply at init: this is where a theme takes effect.
const chart = echarts.init(el, 'brand');
// Themes are also available as JSON files you can build at compile time:
// import darkTheme from './themes/dark.json';
// echarts.registerTheme('dark', darkTheme);| Theme key | Affects | Overridden by |
|---|---|---|
color | The default series palette | A series' own itemStyle.color |
textStyle | All text defaults | Any component-level textStyle |
title / legend / tooltip | Those components | The component's own option |
categoryAxis / valueAxis | Axis appearance | The axis option |
backgroundColor | The canvas fill | The container's CSS background (leave transparent) |
💡
A theme is a set of defaults, not a stylesheet. Anything you set in the option object wins, so a component that hard-codes
color: '#333' will stay dark on a light background no matter which theme is registered. Keeping colour out of the option is what makes theming possible.Switching at runtime
// For a light/dark palette that changes together with the site, use a
// dynamic theme: a function that returns the theme based on a value.
echarts.registerTheme('adaptive', (value) => {
const dark = value === 'dark';
return {
color: dark
? ['#a78bfa', '#38bdf8', '#fbbf24', '#34d399', '#f87171']
: ['#6d28d9', '#0ea5e9', '#f59e0b', '#10b981', '#ef4444'],
textStyle: { color: dark ? '#e2e8f0' : '#0f172a' },
valueAxis: {
axisLabel: { color: dark ? '#94a3b8' : '#475569' },
splitLine: { lineStyle: { color: dark ? '#1e293b' : '#e2e8f0' } }
}
};
});
const chart = echarts.init(el, 'adaptive');
chart.setTheme('dark'); // re-applies the theme with the new value
// Respond to the operating system preference
const media = window.matchMedia('(prefers-color-scheme: dark)');
function apply(matches) { chart.setTheme(matches ? 'dark' : 'light'); }
apply(media.matches);
media.addEventListener('change', (event) => apply(event.matches));// When the site theme is already a set of CSS custom properties, read them
// and build the chart colours from the same tokens. One source of truth.
function themeFromCss(root = document.documentElement) {
const styles = getComputedStyle(root);
const token = (name, fallback) => styles.getPropertyValue(name).trim() || fallback;
return {
color: [
token('--chart-1', '#6d28d9'),
token('--chart-2', '#0ea5e9'),
token('--chart-3', '#f59e0b')
],
textStyle: { color: token('--text-primary', '#0f172a') },
valueAxis: {
axisLabel: { color: token('--text-secondary', '#475569') },
splitLine: { lineStyle: { color: token('--border-subtle', '#e2e8f0') } }
}
};
}
echarts.registerTheme('tokens', themeFromCss());
// Re-register and re-apply when the site switches theme. registerTheme with
// the same name replaces the previous definition.
function refreshChartTheme(chart) {
echarts.registerTheme('tokens', themeFromCss());
chart.setTheme('tokens');
}setThemere-creates the chart's visual state from the option, so any state held in the option survives but transient state such as the current zoom is reset.- Because
setThemere-renders, call it once per theme change rather than on every resize — a resize only needsresize(). - If the container background is provided by CSS, leave
backgroundColor: 'transparent'in the theme. Setting a colour in both places produces a visible seam at the chart edges. - A theme cannot change the chart's structure. Series types, axes and grids are option concerns; a theme only changes how they look.
Overriding individual visuals
// Anything the theme sets can be overridden at the option level, and the
// option wins. This is how a single chart deviates from the house style.
const option = {
color: ['#0f766e'], // override the palette for this chart
textStyle: { fontFamily: 'Georgia, serif' },
legend: { textStyle: { color: '#0f172a', fontWeight: 600 } },
series: [{
type: 'line',
smooth: true,
lineStyle: { width: 3, color: '#0f766e' },
itemStyle: { color: '#0f766e', borderColor: '#fff', borderWidth: 2 },
areaStyle: {
// A gradient supplied as an object, not a CSS string
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(15, 118, 110, 0.28)' },
{ offset: 1, color: 'rgba(15, 118, 110, 0)' }
]
}
},
emphasis: { focus: 'series' } // dim the others on hover
}]
};
// Reusable style blocks keep an option readable
const cardTitle = { textStyle: { fontSize: 14, fontWeight: 600 } };
const axisLine = { lineStyle: { color: '#cbd5e1' } };| Goal | Where it belongs | Why |
|---|---|---|
| Brand palette across all charts | Theme color | One place to change |
| Text sizes and fonts | Theme textStyle | Consistent across components |
| One chart with a different emphasis | Option lineStyle | Local deviation |
| A colour from a CSS design token | A theme built by reading custom properties | Shares the site's source of truth |
| Dark mode | Second theme plus setTheme | Themes are the supported switch |
| Hover dimming | Series emphasis.focus | An interaction, not a colour |
The practical rule for a design system: describe everything shared in a theme object, and keep only per-chart variation in the option. Then a rebrand is one file, and a dark mode is one more, rather than a search across every chart's configuration.
FAQ
Why does setTheme not change my chart's colours?
The option is overriding the theme. A series with an explicit
itemStyle.color keeps it. Remove the hard-coded colours from the option so the theme has something to apply.Can I use CSS variables directly in the option?
No. The option is passed to the canvas renderer, which has no CSS context. Read the computed value with
getComputedStyle and build a theme object from it, then re-register and call setTheme when the tokens change.Related
Option configuration Accessibility, export and printing
Last refreshed 2026-09-18.