Skip to main content

Styling and theming

You will learn
  • The full styling pipeline, in one sentence
  • Why as (style) and axis (scale) are deliberately separate props
  • Which of the three theming approaches fits your app

Chapter 1 teased as="primary". Here's the whole pipeline it belongs to:

a draw layer's column → its as role → theme.line[as] ?? theme.line.default.

Every chart on this page runs it live — this site's own useSiteChartTheme is the same chart you're about to see:

src/examples/learn-02-dual-axis.tsx
import {
ChartContainer,
ChartRow,
Layers,
LineChart,
YAxis,
} from '@pond-ts/charts';
import { useSiteChartTheme } from '@site/src/theme/useSiteChartTheme';
import { singleHostSeries } from './lib/server-metrics';

export default function DualAxis() {
const theme = useSiteChartTheme();
const series = singleHostSeries();

return (
<ChartContainer range={series.timeRange()} width={560} theme={theme}>
<ChartRow height={220}>
<YAxis id="pct" side="left" label="cpu" format=".0%" />
<YAxis id="ms" side="right" label="latency (ms)" format=",.0f" />
<Layers>
<LineChart series={series} column="cpu" axis="pct" as="primary" />
<LineChart
series={series}
column="latency"
axis="ms"
as="secondary"
/>
</Layers>
</ChartRow>
</ChartContainer>
);
}
<LineChart series={series} column="cpu" axis="pct" as="primary" />
<LineChart series={series} column="latency" axis="ms" as="secondary" />

as="primary" and as="secondary" are semantic roles — not colours. The active ChartTheme maps each role to a concrete style ({ color, width, dash? }); change the theme and every chart using that role repaints, with zero code changes. There are no per-component colour props — a <LineChart> doesn't take a color. One styling channel, not two fighting each other.

as picks style; axis picks scale

These are two different props for a reason: as says which style a mark resolves to; axis says which YAxis scale it draws against (chapter 2). A layer's style and its scale are independent — two series can share a style and draw on different axes, or share an axis and carry different styles. Conflating them is exactly the kind of prop-soup composed charts are trying to avoid.

Three ways to build a theme

Your app...Use
Has one fixed look, no design-system tokensSpread defaultTheme, override the slots you want
Themes from CSS custom properties (--brand-*)cssVarTheme(base, resolve)
Has a live dark/light toggle to followuseChartTheme(base, resolve) — re-resolves on toggle

This docs site is the third case — useSiteChartTheme (which every embedded example on this page calls) is a thin wrapper around useChartTheme reading this site's own --pond-viz-* tokens. Toggle the site's dark mode switch and the chart above repaints with no mode prop threaded anywhere.

The full pipeline — custom theme objects, the dash style for model-vs-observed lines, the repaint contract, all the theme slots (band, area, scatter, box, bar, annotation, …) — is Theming charts. This chapter is the map; that recipe is the territory.

Recap

Style flows column → as → theme.line[as] ?? default; as and axis are separate concerns by design; and which theming approach you reach for depends on whether your app has CSS tokens, a live toggle, or neither.

That's chapters 1 through 5 — the core vocabulary: compose a container from rows, layers, and axes; bring your own data; shape it with pond's transforms; style it by role. Every chart you build from here on is this same shape, just deeper.

Next: Reading and selecting values — everything so far has been about drawing a chart. This is where you start interacting with one.