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 keyAffectsOverridden by
colorThe default series paletteA series' own itemStyle.color
textStyleAll text defaultsAny component-level textStyle
title / legend / tooltipThose componentsThe component's own option
categoryAxis / valueAxisAxis appearanceThe axis option
backgroundColorThe canvas fillThe 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');
}
  • setTheme re-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 setTheme re-renders, call it once per theme change rather than on every resize — a resize only needs resize().
  • 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' } };
GoalWhere it belongsWhy
Brand palette across all chartsTheme colorOne place to change
Text sizes and fontsTheme textStyleConsistent across components
One chart with a different emphasisOption lineStyleLocal deviation
A colour from a CSS design tokenA theme built by reading custom propertiesShares the site's source of truth
Dark modeSecond theme plus setThemeThemes are the supported switch
Hover dimmingSeries emphasis.focusAn 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.

Option configuration Accessibility, export and printing

Last refreshed 2026-09-18.