oks-ui

How to use

Chart

Chart covers six chart types (line, area, bar, column, pie, donut) behind one component and a row-based data API — hand it an array of plain objects plus which fields are the category and the series, and it figures out the rest. Every data point is keyboard-reachable and announces its value, not just hover-only like most charting libraries.

import { Chart } from "oks-ui";

Row-based data

The common path: pass data as a plain array of objects, x as the field holding the category (a date, a label), and series for the field(s) to plot. No need to pre-shape data into a categories/series structure — Chart derives it from your rows.

<Chart
  type="line"
  title="Monthly signups"
  data={[
    { month: "Jan", signups: 42 },
    { month: "Feb", signups: 58 },
    { month: "Mar", signups: 51 },
    { month: "Apr", signups: 67 },
  ]}
  x="month"
  series="signups"
/>

Multiple series

series accepts an array for a multi-line/multi-bar chart — pass an array of accessors (or ChartSeriesDef objects with their own label/color) and Chart plots each one, auto-enabling the legend once there's more than one.

<Chart
  type="bar"
  data={salesData}
  x="quarter"
  series={[
    { key: "revenue", label: "Revenue" },
    { key: "cost", label: "Cost" },
  ]}
/>

Projected/forecast segments

A series entry can mark itself isProjection to render as a distinct dashed segment — for a forecasted continuation of real data (actuals through today, a projected trend afterward) without needing two separate charts stitched together. Bar and column charts split the same way, rendering projected bars with their own visual treatment on the same axis scale as the real values.

<Chart
  type="line"
  data={[...actuals, ...forecast]}
  x="month"
  series={[
    { key: "revenue", label: "Actual" },
    { key: "projectedRevenue", label: "Forecast", isProjection: true },
  ]}
/>

Axis, legend, and tooltip options

axis/axisX/axisY control tick visibility and formatting independently per axis; legend and tooltip each accept either a boolean to toggle them outright or an options object for finer control (position, formatting). dataFormat applies a shared prefix/suffix/decimal-place format across labels and tooltips at once, for currency or percentage values.

<Chart
  type="column"
  data={data}
  x="month"
  series="revenue"
  dataFormat={{ prefix: "$", decimals: 0 }}
  legend={{ position: "bottom" }}
/>

Zoom, export, and fullscreen

Three opt-in interaction features, each disabled by default: zoom enables axis zoom/pan (mode: "x" for horizontal-only, "xy" for both, plus wheel/pan toggles); export adds an action that downloads the chart as SVG or PNG; fullscreen adds a toggle that expands the chart to fill the viewport. All three default to false/off, since not every chart context wants the extra chrome.

<Chart
  type="line"
  data={data}
  x="date"
  series="value"
  zoom={{ mode: "x" }}
  export={{ formats: ["png", "svg"] }}
  fullscreen={{ enabled: true }}
/>

Loading, error, and empty states

isLoading, error, and empty each swap in a dedicated state in place of the plotted chart — pass them straight from whatever data-fetching hook is feeding the chart's data, instead of conditionally rendering Chart itself and losing its layout/sizing while the state changes.

<Chart type="line" data={data ?? []} x="month" series="value" isLoading={isLoading} error={fetchError && "Couldn't load data."} />

Accessibility

  • Every data point is keyboard-reachable — Tab/arrow-key navigation moves between points, and onPointClick fires the same way for a keyboard activation (Enter/Space) as it does for a mouse click.
  • Pass ariaLabel describing what the chart shows ("Monthly signups by quarter") — a chart with no accessible name is announced generically, which tells a screen reader user nothing about its content.
  • title/description render as real visible text (not just a tooltip), doubling as a readable summary for anyone who can't perceive the plotted shape itself.
  • The legend, when present, is independently keyboard-operable — toggling a series's visibility via onLegendChange works the same from keyboard as from a mouse click on the legend swatch.

Props reference

All available props for Chart. See the full component page for an interactive playground.

Props

data

Row[] | { categories, series }

Default:

Row-based data array, or explicit categories/series (required).

x

keyof Row | ((row: Row) => ChartCategory)

Default:

Category accessor, row-mode only.

series

ChartSeriesDef<Row> | ChartSeriesDef<Row>[]

Default:

Series accessor(s), row-mode only.

title / description

ReactNode

Default:

Chart heading text.

ariaLabel

string

Default:

Accessible label for the chart.

dataFormat

ChartDataFormatOptions

Default:

Value formatting (prefix/suffix/decimals) for labels/tooltips.

showLabels

ChartShowLabelsOptions

Default:

Inline value-label visibility.

Best practices

  • Use dataFormat once for consistent number formatting across labels/tooltips instead of pre-formatting values in your own data — keeping raw numbers in data lets Chart handle formatting, sorting, and axis scale correctly.
  • Only enable zoom for charts genuinely long enough to benefit from it — dense time-series data, mostly. Turning it on for a 4-bar comparison chart just adds interaction surface with nothing to zoom into.
  • Reach for pie/donut only for a small number of categories (roughly five or fewer) where part-to-whole is the actual point — beyond that, a bar chart reads more accurately and doesn't force awkward slice-color distinctions.
  • Use isProjection for genuinely forecasted/estimated data, not just to stylistically differentiate two real series — the dashed treatment specifically communicates "this part isn't actual measured data."
  • Always pass title or ariaLabel (ideally both) — an unlabeled chart is one of the least accessible patterns in a data-heavy UI.
View full API reference for Chart