Skip to main content

Theming

functions@pond-ts/charts

useChartThemesource

useChartTheme(base: ChartTheme, resolve: (readVar: VarReader) => { annotation?: { color?: string; depth?: readonly [number, number, number]; fillOpacity?: number }; area?: { default?: { color?: string; fill?: string; fillOpacity?: number; width?: number } }; axis?: { band?: { divider?: string; fill?: string; label?: string }; grid?: string; gridDash?: readonly number[]; label?: string; sessionDivider?: string; title?: { color?: string; opacity?: number; size?: number } }; background?: string; band?: { default?: { fill?: string; opacity?: number } }; bar?: { default?: { fill?: string; gap?: number; highlight?: string; minWidth?: number; opacity?: number; outlineWidth?: number } }; box?: { default?: { fill?: string; fillOpacity?: number; median?: string; medianWidth?: number; stroke?: string; strokeWidth?: number; whisker?: string; whiskerWidth?: number } }; candle?: { default?: { bodyWidth?: number; falling?: { body?: string; wick?: string }; neutral?: { body?: string; wick?: string }; rising?: { body?: string; wick?: string }; wickWidth?: number } }; chip?: { background?: string }; cursor?: string; font?: { family?: string; size?: number }; gap?: { connectorOpacity?: number }; line?: { default?: { color?: string; dash?: readonly number[]; width?: number } }; scatter?: { default?: { color?: string; label?: string; outline?: string; outlineWidth?: number; radius?: number; selectedOutline?: string; selectedWidth?: number } } }, opts?: UseChartThemeOptions): ChartTheme

Live ChartTheme bound to CSS custom properties: resolves resolve against the DOM (via cssVarTheme) and re-resolves whenever the theme toggle flips — a MutationObserver watches target's data-theme / class, so <ChartContainer theme={useChartTheme(...)} /> follows dark/light with no mode prop threaded through and no hand-ordered attribute-then-read dance.

When the resolved theme changes it returns a new reference, which is the repaint signal — ChartContainer redraws when handed a new theme. A watched mutation that doesn't change the resolved values (e.g. an app toggling an unrelated class on <html>) returns the same reference, so it doesn't repaint. Resolution runs on mount and on watched-attribute changes only — never per frame — so the getComputedStyle read stays cheap.

const theme = useChartTheme(defaultTheme, (v) => ({
  line: { default: { color: v('--td-primary') } },
  axis: { label: v('--td-text-3'), grid: v('--td-hairline') },
}));
return <ChartContainer width={w} theme={theme}>…</ChartContainer>;

base and resolve are read fresh on every resolve (held in refs), so inline literals are fine — they don't need memoizing and don't re-subscribe the observer. But because a resolve only fires on mount + a watched mutation, changing base/resolve alone won't re-resolve until the next toggle; if you need to swap them and re-resolve immediately, change target / attributes (which re-subscribes) or remount. SSR-safe: the first value resolves with no DOM (returns base + any literal fallbacks); the client re-resolves on mount.

cssVarThemesource

cssVarTheme(base: ChartTheme, resolve: (readVar: VarReader) => { annotation?: { color?: string; depth?: readonly [number, number, number]; fillOpacity?: number }; area?: { default?: { color?: string; fill?: string; fillOpacity?: number; width?: number } }; axis?: { band?: { divider?: string; fill?: string; label?: string }; grid?: string; gridDash?: readonly number[]; label?: string; sessionDivider?: string; title?: { color?: string; opacity?: number; size?: number } }; background?: string; band?: { default?: { fill?: string; opacity?: number } }; bar?: { default?: { fill?: string; gap?: number; highlight?: string; minWidth?: number; opacity?: number; outlineWidth?: number } }; box?: { default?: { fill?: string; fillOpacity?: number; median?: string; medianWidth?: number; stroke?: string; strokeWidth?: number; whisker?: string; whiskerWidth?: number } }; candle?: { default?: { bodyWidth?: number; falling?: { body?: string; wick?: string }; neutral?: { body?: string; wick?: string }; rising?: { body?: string; wick?: string }; wickWidth?: number } }; chip?: { background?: string }; cursor?: string; font?: { family?: string; size?: number }; gap?: { connectorOpacity?: number }; line?: { default?: { color?: string; dash?: readonly number[]; width?: number } }; scatter?: { default?: { color?: string; label?: string; outline?: string; outlineWidth?: number; radius?: number; selectedOutline?: string; selectedWidth?: number } } }, opts?: { element?: Element }): ChartTheme

Build a ChartTheme by overlaying CSS-custom-property values onto a base theme — the adapter that lets a chart track a design system's tokens (and its dark/light toggle) without hand-mirroring hex values.

resolve receives a VarReader and returns only the slots to override (a ChartThemeOverrides); the result is base deep-merged with them. The typed ChartTheme stays the one styling channel — this generates it from CSS, it doesn't add a second one.

const theme = cssVarTheme(defaultTheme, (v) => ({
  line: { default: { color: v('--td-primary') }, secondary: { color: v('--td-secondary') } },
  axis: { label: v('--td-text-3'), grid: v('--td-hairline') },
  cursor: v('--td-text-3'),
  font: { family: v('--td-font-mono') },
}));

DOM-only, by design. It reads getComputedStyle off opts.element (or document.documentElement). With no DOM — SSR, an OffscreenCanvas worker — every readVar returns its fallback (or undefined, kept from base), so the call is safe and returns the base theme (plus any literal fallbacks). For a live chart that follows a theme toggle, use useChartTheme, which wraps this and re-resolves on a data-theme change — don't call cssVarTheme per frame (getComputedStyle is a layout read).