Color has one semantic path. A mark's color channel contributes values to the chart-level color scale and legend. z partitions series or interaction groups and supplies the color value only when color is omitted. fill and stroke are final paint overrides; using either bypasses scale mapping for that paint.
When marks emit categorical color values and no color scale is supplied, TanStack Charts uses the chart theme palette. This is the convenient default for a small, stable set of categories.
For persistent product semantics, supply an explicit configured D3 ordinal scale:
import { scaleOrdinal } from 'd3-scale'
import { colorLegend, defineChart, lineY } from '@tanstack/charts'
const series = ['core', 'react', 'octane'] as const
const color = scaleOrdinal<string, string>()
.domain(series)
.range(['#2563eb', '#f97316', '#10b981'])
const definition = defineChart({
marks: [
lineY(rows, {
x: 'date',
y: 'value',
z: 'series',
}),
],
x,
y,
color: {
scale: color,
legend: colorLegend({ label: 'Package' }),
},
})The application owns the domain order and paint assignment. This prevents a category from changing color when data is filtered or reordered.
Supply a D3 color-scale factory and put the numeric field on the mark's color channel:
import { scaleSequential } from 'd3-scale'
import { interpolateBlues } from 'd3-scale-chromatic'
import { colorLegend } from '@tanstack/charts'
const color = {
scale: () => scaleSequential(interpolateBlues),
legend: colorLegend({
label: 'Requests per minute',
format: (value) => value.toLocaleString(),
}),
}The factory keeps the interpolator and lets the chart infer the numeric domain from color-channel values. Continuous and quantize factories infer a finite extent. Quantile factories receive the complete observed numeric population, including duplicates. Threshold factories require an explicit domain because their cuts are policy, not an extent:
import { scaleThreshold } from 'd3-scale'
const colors = ['#eff6ff', '#bfdbfe', '#60a5fa', '#1d4ed8']
const color = {
scale: scaleThreshold<number, string>,
domain: [5, 12, 24],
range: colors,
legend: colorLegend({ label: 'Incidents' }),
}d3-scale-chromatic and its matching type package are optional direct application dependencies. They are never pulled into charts that do not import them. The D3 integration page owns the install and API-reference links.
colorLegend reads the resolved scale:
Options:
The legend reserves its own layout height. It is visual guidance and is hidden from the SVG accessibility tree; essential category meaning should also be available through direct labels, surrounding HTML, or a table.
colorGradientLegend requires a numeric color-scale domain. Options:
Use colorGradientLegend only when a discrete scale should intentionally be shown as a sampled ramp. It requires a numeric domain and does not invent units or semantic thresholds.
Prefer direct labels when:
Prefer a legend when:
It is valid to use both when the legend establishes the complete domain and direct labels help with the primary comparison.
See Themes and Styling and Accessibility for the surrounding policies.