Recipe

Register a brand theme across Apex products

There is no family-wide theme registry. Three products each have their own registerTheme, two have none at all, and the name you hand to all of them resolves in separate stores that cannot see each other.

One brand registered three ways across ApexCharts and ApexSankey, with the contrast of each label read back liveOpen in new tab

Built with ApexCharts.js, ApexSankey, ApexStock, ApexGrid

Register the brand once per product, in that product's own registry, with that product's own theme definition. There is no family-wide theme registry: ApexCharts, ApexSankey and ApexStock each expose a separate registerTheme static, ApexMaps has none at all, and ApexGrid is not a global object to register into, so one name handed to all of them is not one theme. It is several lookups into stores that cannot see each other.

Everything below was measured in Chromium against apexcharts 7.8.0, apexsankey 1.12.2, apexstock 0.5.1, apexmaps 1.0.0 and apex-grid 3.5.0, by reading computed style back off the rendered SVG rather than by reading the docs.

Which products have a theme registry at all?

Three of the five, and no two of them agree on the shape of a theme or on how you name one.

LibraryWhere a theme is registeredHow a registered name is referencedCan a name be removed?
ApexCharts 7.8.0ApexCharts.registerTheme(name, def)theme: { name: 'brand' }Yes: ApexCharts.unregisterTheme(name)
ApexSankey 1.12.2ApexSankey.registerTheme(name, theme)theme: 'brand'No. unregisterTheme is undefined
ApexStock 0.5.1ApexStock.registerTheme(name, preset)theme: { preset: 'brand' }, or setThemePreset(name)No. unregisterTheme is undefined
ApexMaps 1.0.0Nothing. registerTheme is undefinedn/an/a
apex-grid 3.5.0Nothing. There is no globaln/an/a

Two rows need their own sentence.

ApexMaps has four register* statics and none of them is a theme. They are registerMap, registerLayout, registerProjection and registerPalette. A registered palette is the nearest thing it has to a brand preset, and a palette is not a theme: see its theming docs for what it does take.

ApexGrid is a custom element, so there is nothing to register into. Its own shipped element documentation says there is no theme to import and no configureTheme() call. Its brand hook is the --ag-brand custom property, which theme="tinted" mixes into the chrome surfaces, and its mode hook is theme="dark" on the host, combinable as theme="dark tinted". The ApexGrid styles and themes guide owns that surface.

The removal column is the one that bites later. unregisterTheme is a function on ApexCharts and undefined on both of the others, so on ApexSankey and ApexStock re-registering the same name is the only runtime way to change what that name means.

The definitions have no keys in common either, which is why a name cannot be portable:

LibraryTheme definitionKeys
ApexChartsApexThemeDefmode, palette, tokens (accent, fore, grid, surface, series), monochrome, accessibility
ApexSankeySankeyThemenodePalette, fontColor, edgeOpacity, edgeGradientFill, nodeBorderColor, canvasStyle, tooltipTheme
ApexStockThemePresetmode, up, down, grid, axis, background, accent

Even the return values differ. ApexCharts.registerTheme returns the ApexCharts class so it chains, ApexSankey.registerTheme returns undefined, and ApexStock.registerTheme returns the stored preset normalised to all seven keys, which is the only one of the three that tells you what it actually kept.

Why not register it once and pass the name?

Because it half-works, and the half that fails is the half you are not looking at.

Registering a Sankey-shaped definition in the chart registry reaches nothing. It is the obvious first attempt, because the family already shares a licence slot and a design-token layer, so a shared theme registry is a fair guess:

ApexCharts.registerTheme('x1', {
  nodePalette: ['#7c5cff', '#00b8a9', '#ffb703'],
  fontColor: '#ffffff',
  canvasStyle: 'background: #101828;',
})
new ApexSankey(el, { theme: 'x1', width: 560, height: 300 })
  .render({ nodes, edges })

Measured on apexsankey 1.12.2, that diagram is identical to one built with theme: 'does-not-exist': first node fill rgb(107, 158, 206), label rgb(33, 33, 33), SVG background rgba(0, 0, 0, 0) and the inline style border: 1px solid #caced0; box-sizing: border-box, which is the hardcoded light chrome. The reverse direction is just as dead. A chart-shaped definition registered with ApexSankey.registerTheme and then named on a chart left that chart identical to a name registered nowhere (axis label rgb(55, 61, 63), background rgba(0, 0, 0, 0), first stroke rgba(0, 143, 251, 0.85)), and naming it on a Sankey left the diagram at its defaults too.

Register the same name in both registries, each with its own definition, and both are themed completely: both canvases came back rgb(11, 15, 25) and both label colours rgb(238, 238, 238). That is the decision, and it costs three registrations and one custom property instead of one call.

What breaks first?

Two things, and both are silent. Zero console.error calls and zero pageerror events were raised in any of the runs below.

Half one: dark already means something

'dark' is one of five built-in theme names on ApexSankey 1.12.2, alongside light, midnight, mint and sunset. It is not a registered ApexCharts theme, and an unknown theme.name on a chart is not an error, it is a no-op.

So passing the single name 'dark' to both sends the diagram fully dark (canvas rgb(14, 26, 35), inline style background: #0e1a23; border: 1px solid #1f2f3a; box-sizing: border-box;, labels rgb(231, 239, 244)) while the chart does not move at all. The chart output matched a run with the name 'does-not-exist' on every value read.

Dark mode on a chart is theme.mode, which is a different option from theme.name, and theme: { mode: 'dark' } does repaint the chart. The dark mode dashboard resolves Light, Dark and System once across three products and shows the surface each one paints.

Name collisions are not hypothetical, either. mint is a built-in on ApexSankey and a built-in preset on ApexStock 0.5.1, whose six are paper, arctic, mint, linen, rose and graphite. One string, two libraries, two unrelated looks, no warning from either.

Half two: near-white labels on an unpainted canvas

This is the one that reaches production. A theme registered through ApexCharts.registerTheme is not completely ignored by a Sankey. Exactly two keys of it cross over, and they are the two that make the mistake look like it nearly worked.

Registering one token key at a time and rendering a Sankey under that name gave:

Key in the chart definitionWhat it did to the Sankey
tokens.foreBecame the label colour
tokens.gridBecame the node border colour
tokens.accentNothing
tokens.surfaceNothing. The canvas stayed rgba(0, 0, 0, 0)
tokens.seriesNothing. Node fills stayed at the built-in palette
paletteNothing

So a dark brand whose tokens.fore is #eeeeee paints rgb(238, 238, 238) labels onto a canvas that was never painted, which means onto whatever your page is. Against a white body that is 1.16:1, computed from the measured label fill and the measured backdrop. The same label against the #0b0f19 surface the theme intended is 16.51:1, so the colour is right and only the surface is missing.

That is why this survives review. Anyone building a dark theme is looking at a dark page, where 16.51:1 is exactly what they expect to see. The demo at the top of this page has that on a switch: pick "One name, chart registry only", then tick "Put the panels on the dark surface" and watch the identical broken registration go from unreadable to perfect without a line of code changing.

Two details worth knowing about that crossover. It is not the CSS token layer wearing a disguise: after the registerTheme call and after the render, document.documentElement still had no inline style and --apx-fore computed to an empty string on the root, on the body and on the Sankey's own host. And ApexCharts.unregisterTheme('brand') removes what the Sankey was reading as well, which put the label back to rgb(33, 33, 33). For which CSS custom properties a Sankey genuinely reads from the cascade, and in what order, see the ApexSankey theming docs; for the chart's own resolution order, the ApexCharts themes guide; and for which products read the family token layer at all, one key, one palette, one engine.

How do I register one brand across all of them?

Write the brand once as plain data, then translate it into each product's own definition. The translation is the work, and it is one object plus three calls.

// npm install apexcharts@7.8.0 apexsankey@1.12.2 apexstock@0.5.1 apex-grid@3.5.0
import ApexCharts from 'apexcharts'
import { ApexSankey } from 'apexsankey'
import ApexStock from 'apexstock'
import 'apex-grid/define'

const BRAND = {
  series: ['#7c5cff', '#00b8a9', '#ffb703'],
  ink: '#eeeeee',
  line: '#334155',
  surface: '#0b0f19',
  accent: '#7c5cff',
}

// 1. ApexCharts: ApexThemeDef. Returns the class, so it chains.
ApexCharts.registerTheme('brand', {
  mode: 'dark',
  palette: BRAND.series,
  tokens: {
    accent: BRAND.accent,
    fore: BRAND.ink,
    grid: BRAND.line,
    surface: BRAND.surface,
  },
})

// 2. ApexSankey: SankeyTheme. canvasStyle is the key with no counterpart
//    above, and it is the one that paints the diagram.
ApexSankey.registerTheme('brand', {
  nodePalette: BRAND.series,
  fontColor: BRAND.ink,
  nodeBorderColor: BRAND.line,
  edgeOpacity: 0.45,
  canvasStyle: `background: ${BRAND.surface}; border: 1px solid ${BRAND.line};`,
  tooltipTheme: 'dark',
})

// 3. ApexStock: ThemePreset. Candle up/down have no counterpart anywhere else.
ApexStock.setApexCharts(ApexCharts)
ApexStock.registerTheme('brand', {
  mode: 'dark',
  up: BRAND.series[1],
  down: '#ff5a5f',
  grid: BRAND.line,
  axis: BRAND.ink,
  background: BRAND.surface,
  accent: BRAND.accent,
})

// 4. ApexGrid has no registry. Its brand is a CSS custom property and its
//    mode is an attribute on the host: <apex-grid theme="dark tinted">.
document.documentElement.style.setProperty('--ag-brand', BRAND.accent)

// Then, three different ways to say the same word.
const chart = new ApexCharts(chartEl, {
  chart: { type: 'area', height: 300 },
  series: chartSeries,
  theme: { name: 'brand' },
})
await chart.render()

const sankey = new ApexSankey(sankeyEl, { theme: 'brand', width: 560, height: 300 })
sankey.render({ nodes, edges })

const stock = new ApexStock(stockEl, {
  chart: { height: 420 },
  series: [{ name: 'ACME', data: ohlc }],
  theme: { preset: 'brand' },
})

// The assertion. If the second line is not a colour, the Sankey half of the
// registration never happened, and nothing will have told you.
console.log(getComputedStyle(chartEl.querySelector('svg')).backgroundColor)
// rgb(11, 15, 25)
console.log(getComputedStyle(sankeyEl.querySelector('svg')).backgroundColor)
// rgba(0, 0, 0, 0)  <- broken.  rgb(11, 15, 25) <- correct.

Paste those last four lines into any page that themes both. A transparent Sankey canvas next to a painted chart canvas is the signature of a brand that was registered once and named twice.

When should I do something else?

If what you want is a light and dark mode, not a brand, none of this is worth doing.

What you wantReach for
Light, Dark and System across a page of productsOne resolver that pushes a mode, as in the dark mode dashboard. One switch, not four registrations.
A chart that follows the operating system with no JavaScripttheme.follow: 'os' on the chart, in the themes guide
Colours that come from your design system's CSSThe family token layer, covered in one key, one palette, one engine
One diagram, one look, used onceAn inline theme object. ApexSankey's theme accepts a SankeyTheme directly, so a name you use once buys nothing
Grid chrome only--ag-brand and the theme attribute, in styles and themes
A stock chart onlysetThemePreset(name) at runtime, in ApexStock theming

Registering a brand is worth it when the same name has to be applied in many places, by people who will not be reading this page, and when you want one object in your codebase that every product's look is derived from.

Which plans include these?

The libraries on this page are not on one plan, which is another reason the single-call version of this recipe does not exist.

LibraryPlan
ApexCharts.jsCommunity and up
ApexGridCommunity and up
ApexMapsCommunity and up
ApexSankeyPro and up
ApexStockPremium and up

Community is the entry plan and is free only for organizations under $2M USD in annual revenue, budget or funding; at or above that threshold it is a paid licence like the others. Nothing in this family is open source, and source published on GitHub is not an open licence. All five render in full without a licence key, watermarked, so every measurement on this page can be reproduced before any of it matters. The pricing page has the full matrix.

One resolver pushing a mode to three products, which is the other half of this problem

See the pieces running

Reference documentation

Frequently Asked Questions

Is there one registerTheme for the whole ApexCharts family?

No. Measured on the installed packages, registerTheme is a separate static on three products and absent from two: it is a function on ApexCharts 7.8.0, ApexSankey 1.12.2 and ApexStock 0.5.1, undefined on ApexMaps 1.0.0, and apex-grid 3.5.0 exposes no global object at all because it is a custom element. The three registries take three different definition shapes and are referenced three different ways, so a brand has to be registered once per product under the same name.

Why does my ApexSankey diagram ignore the theme I registered?

Because it was almost certainly registered in the wrong store. A theme registered with ApexCharts.registerTheme and then named on a Sankey produced output identical to a name registered nowhere on apexsankey 1.12.2: first node fill rgb(107, 158, 206), label rgb(33, 33, 33), SVG background rgba(0, 0, 0, 0) and the hardcoded light chrome inline style. The reverse is equally dead. Register the same name a second time with ApexSankey.registerTheme and a SankeyTheme definition.

Why did passing theme 'dark' turn my Sankey dark but do nothing to my chart?

Because 'dark' is one of five built-in theme names on ApexSankey 1.12.2 (light, dark, midnight, mint and sunset) and is not a registered ApexCharts theme. An unknown theme.name on a chart is a no-op, not an error: the chart output matched a run named 'does-not-exist' on every value read, with zero console errors and zero pageerror events. Dark mode on a chart is theme.mode, a different option.

Why are my Sankey node labels invisible?

Because a chart-registry theme passes exactly two of its keys to a Sankey and the canvas is not one of them. Registering one token key at a time on apexcharts 7.8.0 showed tokens.fore becoming the label colour and tokens.grid the node border, while tokens.accent, tokens.surface, tokens.series and palette reached nothing. So a dark brand whose tokens.fore is #eeeeee paints rgb(238, 238, 238) labels on a canvas that stayed rgba(0, 0, 0, 0), which is 1.16:1 against a white page. The same label on the #0b0f19 surface the theme intended is 16.51:1, so the colour is right and only the surface is missing.

How do I remove or replace a registered theme at runtime?

Only ApexCharts can remove one. unregisterTheme is a function on ApexCharts 7.8.0 and undefined on both ApexSankey 1.12.2 and ApexStock 0.5.1, so on those two re-registering the same name with a new definition is the only runtime swap available. ApexCharts.unregisterTheme also clears what a Sankey was reading through the two-key crossover, which put its label fill back to the default rgb(33, 33, 33).

How do I brand an ApexGrid or an ApexMaps map, if neither has registerTheme?

Through their own hooks. apex-grid 3.5.0 is a custom element with no global and no theme module: its own element documentation points at the --ag-brand custom property, which theme="tinted" mixes into the chrome surfaces, and theme="dark" on the host for the built-in dark palette, combinable as theme="dark tinted". ApexMaps 1.0.0 has four register statics (registerMap, registerLayout, registerProjection and registerPalette) and no theme registry, so a registered palette is the closest equivalent. The ApexGrid styles and themes guide and the ApexMaps theming docs own those two surfaces.

Related

See the five built-in Sankey themes

The ApexSankey themes demo switches between the built-in names and a registered brand preset, with source, which is the registry this recipe says you cannot skip.

Get started