# TypeScript

TanStack Charts is designed so ordinary chart code names its application types
once. Data, channel outputs, scale domains, focus points, and framework props
then infer from the definition.

## Infer from mark channels

<!-- docs-example: typescript-inference typecheck -->

```ts
import { scaleLinear, scaleTime } from 'd3-scale'
import { defineChart, lineY } from '@tanstack/charts'

interface Reading {
  id: string
  recordedAt: Date
  temperature: number
}

const readings: readonly Reading[] = []

const definition = defineChart({
  marks: [
    lineY(readings, {
      x: 'recordedAt',
      y: 'temperature',
      key: 'id',
    }),
  ],
  x: { scale: scaleTime() },
  y: { scale: scaleLinear() },
})
```

Here the datum is `Reading`, x values are `Date`, and y values are `number`.
An incompatible scale or channel field fails at the definition instead of
surfacing later in a tooltip callback.

## Capture application values

```ts
function createTrafficDefinition(rows: readonly Reading[]) {
  return defineChart({
    marks: [
      lineY(rows, {
        x: 'recordedAt',
        y: 'temperature',
        key: 'id',
      }),
    ],
    x: { scale: scaleTime() },
    y: { scale: scaleLinear() },
  })
}
```

Framework components should memoize the complete definition:

```tsx
const definition = useMemo(() => createTrafficDefinition(readings), [readings])

return <Chart definition={definition} ariaLabel="Temperature history" />
```

Definition identity tells the host when captured application data or options
changed. A responsive definition callback still rebuilds when the host size
changes:

```ts
function createTrafficDefinition(rows: readonly Reading[]) {
  return defineChart(({ width }) => ({
    marks: [
      lineY(rows, {
        x: 'recordedAt',
        y: 'temperature',
        key: 'id',
      }),
    ],
    x: { scale: scaleTime() },
    y: { scale: scaleLinear() },
    margin: width < 480 ? 24 : 40,
  }))
}
```

## Keep literal information

Prefer:

- field-name channels such as `x: 'recordedAt'`;
- typed accessors when a value is derived;
- `defineChart({...})` or `defineChart({...})`;
- `satisfies` when naming a configuration object separately.

Avoid annotating an intermediate object as broad `ChartSpec` before passing it
to `defineChart`. That discards the literal mark tuple used for axis and
callback inference.

## Callback types

Focus and selection callbacks receive the original datum and inferred
coordinate types:

```tsx
<Chart
  definition={definition}
  ariaLabel="Temperature readings"
  onSelect={(point) => {
    if (!point) return
    point.datum.recordedAt // Date
    point.xValue // Date
    point.yValue // number
  }}
/>
```

Do not cast a callback parameter or re-find its datum by key. If inference has
degraded to `unknown`, move back to the definition and look for an erased mark
tuple, an `any` annotation, or an untyped custom scale.

## Extract inferred types

The public type utilities are useful at extension boundaries:

```ts
import type {
  ChartMarkDatum,
  ChartSpecDatum,
  ChartSpecXValue,
  ChartSpecYValue,
} from '@tanstack/charts'
import type {
  ChartMarkPointX,
  ChartMarkPointY,
} from '@tanstack/charts/mark/scale-values'
```

Use them to describe reusable helpers without repeating a datum or coordinate
union manually. The exact utility contracts are listed in
[Types](../reference/types.md).

## Custom marks

`createMark<TDatum, TXValue, TYValue>` keeps interaction points and scale
values aligned for the common case. Use the advanced scale-value factory only
when the materialized axis domain intentionally differs from the point anchor.

See [Custom Marks and Renderers](./custom-marks-and-renderers.md). A custom
extension that requires `as unknown as`, a private import, or suppressed type
errors indicates a missing public boundary and should be reduced to a failing
type test.

## Type tests

Keep positive and negative examples near reusable definitions:

```ts
// @ts-expect-error chart options do not accept formal input
mountChart(container, {
  definition,
  input: { rows: readings },
  ariaLabel: 'Readings',
})
```

Use `@ts-expect-error` only when the test asserts a specific rejected contract.
Do not use it to make application examples compile.

## No-cast checklist

- Datum and input types are declared at the application boundary.
- Channel fields are checked against the datum.
- Scale domains match inferred coordinate types.
- Definitions preserve their literal mark tuple.
- Adapters infer props from the definition.
- Callbacks receive original typed data.
- Custom extensions expose, rather than erase, their generic relationship.
