Theming
functions@pond-ts/chartsuseChartThemesource
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).