Built with ApexCharts.js, ApexGrid, ApexSankey
To put an ApexCharts chart in dark mode, set theme.mode: 'dark', and set
chart.background: 'transparent' alongside it so the chart sits on your page's
surface instead of painting its own. That is the whole answer for one chart.
The dashboard above is the harder case: a page of three products, ApexCharts.js for the charts, ApexGrid for the campaign table and ApexSankey for the visitor journey, behind one Light, Dark and System switch. The three take a theme in three different ways, one of them only at construction time, and that is the part no single product's documentation covers.
How do you enable dark mode in ApexCharts?
const chart = new ApexCharts(el, {
chart: { type: 'area', background: 'transparent' },
theme: { mode: 'dark' },
// ...
})
// switch at runtime, no re-create needed
chart.updateOptions({ theme: { mode: 'light' } }, false, false)
The background line matters more than it looks. With chart.background
unset, the mode picks a background for you: 'dark' paints the SVG #343A3F
and 'light' paints it #fff. On a designed dark page that is a grey
rectangle inside every panel. 'transparent' hands the background back to
your CSS in both modes.
To follow the operating system with no script at all, use
theme: { follow: 'os' }. On a page of charts alone that is the best option.
On this page it is the wrong one, for the reason in the next section.
theme.mode also leaves two things alone. Series colours are yours, so pick a
palette that holds contrast on both surfaces (this dashboard uses one palette
for both modes). And the radialBar track keeps its light grey ring on a dark
page unless you set plotOptions.radialBar.track.background per mode.
The dark mode guide covers React, Next.js and export.
How do you switch a page of different products to dark mode?
One resolver, three mechanisms. Measured on ApexCharts 7.8.0, apex-grid 3.5.0 and ApexSankey 1.12.2:
| Product | How it takes a theme | Follows the OS by itself? | Paints its own surface? |
|---|---|---|---|
| ApexCharts | runtime option, updateOptions({ theme: { mode } }) | with theme.follow: 'os' | yes, #343A3F, unless chart.background is set |
| ApexGrid | attribute, theme="dark" on the element | no | yes, its slate palette, #0f172a |
| ApexSankey | constructor option only, theme: 'dark' | no | yes, #0e1a23, unless canvasStyle overrides it |
So "System" cannot be delegated to each product. If the charts follow the OS on their own while the grid and the Sankey do not, an OS switch at sunset turns a third of the page dark. The page resolves the choice once and tells every product the answer:
const osDark = matchMedia('(prefers-color-scheme: dark)')
const resolve = () => (choice === 'system' ? (osDark.matches ? 'dark' : 'light') : choice)
function applyTheme() {
const mode = resolve()
document.documentElement.dataset.theme = mode // the page's own CSS tokens
for (const c of charts) c.updateOptions(chartTheme(mode), false, false)
if (mode === 'dark') grid.setAttribute('theme', 'dark')
else grid.removeAttribute('theme')
if (sankeyMode !== mode) buildSankey(mode) // rebuild: see below
}
// Only an explicit "System" listens. A reader who chose Dark keeps Dark.
osDark.addEventListener('change', () => { if (choice === 'system') applyTheme() })
The page's own tokens are matched to ApexGrid's built-in dark palette, whose
host paints #0f172a, so the table reads as the same material as the panels
around it rather than a slightly different dark.
Why does ApexSankey need a rebuild to change theme?
Because the theme is read once, in the constructor. Calling
sankey.update() with theme: 'dark' in its options leaves the diagram as it
was: the labels stay #212121 and sankey.options.theme still reads
'light' afterwards. A theme change is a destroy and a new instance:
function buildSankey(mode) {
if (sankey) sankey.destroy()
journeyEl.replaceChildren()
const width = Math.max(320, journeyEl.clientWidth)
sankey = new ApexSankey(journeyEl, {
theme: mode,
width,
height: 300,
viewPortWidth: width, // draw at 1:1, see below
viewPortHeight: 300,
canvasStyle: 'background: transparent; border: 0;', // beats the theme's surface
})
sankey.render({ nodes, edges, options: sankey.options })
sankey.on('node:click', ({ id }) => setChannel(id))
}
Two things make that rebuild cheap to live with. It runs only when the
resolved mode actually changes, not on every click of the switch. And the
same function is the resize handler, because size is a construction-time
input too: the diagram is laid out in an 800 by 500 viewBox by default and
scaled to fit, so in a wide, short panel it shrinks to a third of the width
with tiny labels unless viewPortWidth and viewPortHeight are the real size.
Pass the width as a number. A '100%' width makes ApexSankey apply the same
unit to the height, so height: 300 becomes 300%.
What breaks first
A light flash on load. If the charts are created with default options and
then switched by applyTheme(), a dark page shows white charts for a frame.
Create them with the resolved theme already in their options, then call
applyTheme() for the other two products.
Channel colours that change with the theme. The Sankey's dark theme brings
its own node palette. Setting color on the four channel nodes pins them to
the same colours as the sessions chart, so Search is the same blue in the
chart, the journey and the table in both modes.
A theme preference that cannot be stored. The switch remembers the choice
in localStorage, inside a try, because storage throws in some private
windows and embedded contexts. When it does, the page falls back to its
default instead of failing to start.
Which plan covers this?
ApexCharts.js and ApexGrid are included on every plan, including Community, which is free for organizations under $2M in annual revenue, budget or funding. ApexSankey is included from the Pro plan upward; without a license key it renders with a watermark, so the dashboard can be rebuilt on your own data before buying. Redistributing the libraries inside software you sell or host is covered by the OEM plan.
Every theming option in ApexChartsSee the pieces running
Reference documentation
Frequently Asked Questions
How do I enable dark mode in ApexCharts?
Set theme.mode to 'dark', and set chart.background to 'transparent' alongside it. Without the background line the chart paints its own #343A3F surface, which shows as a grey box on any other dark page. Switch at runtime with chart.updateOptions({ theme: { mode: 'light' } }); there is no need to re-create the chart. To follow the operating system with no script, use theme.follow: 'os'.
Why is there a grey box behind my dark ApexCharts chart?
That is the chart's default dark background. When chart.background is unset, theme.mode 'dark' paints the SVG #343A3F (and 'light' paints it #fff). Set chart.background to 'transparent' to let your container's colour show through.
Does ApexGrid follow the operating system dark mode?
No. Measured on apex-grid 3.5.0, the grid stays light under a dark OS preference until the theme="dark" attribute is set on the element. Resolve the preference in your own code and set the attribute.
Can ApexSankey change theme without being re-created?
No. ApexSankey 1.12.2 reads its theme in the constructor, and calling update() with a different theme in the options leaves the diagram unchanged. Destroy the instance and build a new one with the new theme, and only when the resolved mode actually changed.
Which plans include ApexCharts, ApexGrid and ApexSankey together?
ApexCharts.js and ApexGrid are on every plan, including Community, which is free for organizations under $2M in annual revenue, budget or funding. ApexSankey is included from the Pro plan upward, and renders with a watermark without a license key, so the dashboard can be rebuilt on your own data first. Redistributing them inside software you sell or host requires the OEM plan.
Related
Theme a dashboard of your own
ApexCharts and ApexGrid are on every plan; ApexSankey is Pro and above.