react-chartist: a typed React wrapper that finally matches Chartist 1.x
⚛ React component for Chartist.js
At a glance
- What is it?
- react-chartist wraps Chartist.js in a small typed React component whose props are a discriminated union keyed by the chart type, so data, options and event payloads are typed for the chart you actually drew. Its sharpest edge is that props are compared by reference, so unmemoised objects rebuild the chart on every render.
- Who is it for?
- react-chartist suits a project that has chosen Chartist and wants it in React with real types rather than a loosely typed wrapper, since the prop union and the directly imported Chartist types remove the guesswork from the chart data. It does not suit you if you need a charting library with its own rendering, because this component draws nothing itself and inherits everything about Chartist's output, including its accessibility limits.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A React wrapper that finally has types
react-chartist is described as a small, typed React component for Chartist.js, and there is a live demo hosted alongside the package.
The word typed does most of the work in that sentence, because it is what the version 1 reboot was for. The readme says version 1 is a modern reboot for Chartist 1.x and React 18 and 19, and that it keeps the familiar ChartistGraph API while replacing the legacy Babel build and the old Chartist constructors.
That last part is the substance. Chartist has two incompatible generations, the 0.10 line and the 1.5 line, and they do not share an API. A wrapper that carried the old constructors forward could not have the modern type definitions, because the types come from Chartist itself and the old Chartist had no usable ones. So the upgrade path was Chartist 1.x first and the wrapper second.
The props table makes the typing concrete. The props are a discriminated TypeScript union, so data, options and the event payloads are typed for the selected chart type. The type prop takes the values Line, Bar or Pie and selects which Chartist constructor runs. Data is required and holds the labels and series Chartist renders, and options is optional and updates the existing chart when its reference changes.
Two props behave in ways worth knowing. The responsiveOptions prop takes a list of media query and options pairs, and it recreates the chart when its reference changes. The listener prop is a typed event handler map that adds and removes Chartist event listeners.
Everything else on the div is forwarded. All other div attributes, including the aria attributes, id, role and style, are forwarded to the chart container. And className is appended to the built-in ct-chart class rather than replacing it, which is the detail that lets a layout utility like a twelfth-width column class work.
Installing it takes the component and the chart library, and the chart library's stylesheet has to be imported once in the application:
npm install react-chartist chartistimport "chartist/dist/index.css";The package is MIT licensed, the author is Fraser Xu, and the releases are v1.0.0-rc.1 and v1.0.0, both from July 2026, with the last push on 2026-10-16.
Reference equality decides whether the chart is rebuilt
The single most important behavioural detail in the library is stated in one sentence after the usage example: data, options, responsiveOptions and listener are compared by reference, and you should memoise values that do not need to change so Chartist does not perform unnecessary work.
That is a rule about how the component reacts, and it explains the whole design. A React component re-renders when its props change, and in React a new object literal with the same contents is still a new reference. So a component that renders an inline data object would produce a new reference on every render and the chart would be torn down and rebuilt on every render of the parent.
The usage example shows the intended pattern. The data is built in a useMemo with an empty dependency array:
import { useMemo } from "react";
import ChartistGraph from "react-chartist";
import "chartist/dist/index.css";
export function WeeklySalesChart() {
const data = useMemo(
() => ({
labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
series: [[5, 9, 7, 8, 5]],
}),
[],The options object is built in a second useMemo with an empty array, and the component is given the data, the options and the chart type:
const options = useMemo(
() => ({
fullWidth: true,
low: 0,
showArea: true,
}),
[],
);
return (
<ChartistGraph
aria-label="Weekly sales"
className="ct-major-twelfth"
data={data}
options={options}The labels array and the series array live inside the memo, so they are stable across renders.
So the library delegates the decision: it compares by reference, and the caller is responsible for making the reference stable when nothing has changed. That is a real constraint, and it is the kind of thing that shows up as a performance problem rather than a bug, since the chart looks correct either way.
The difference between the props is worth spelling out. Options updates the existing chart when its reference changes. Responsive options recreates the chart when its reference changes, and the reason is given separately: Chartist only accepts responsive options when constructing a chart, so changing this prop safely detaches and recreates the instance.
So there are two update paths in the library, and they are not interchangeable. A cheap in-place update for options, and a full recreate for responsive options, because the underlying library does not support changing the second kind after construction.
Event listeners now update when the listener prop changes, which is another of the breaking changes listed in the migration section, and the imperative handle exposes the current chart instance and the owned container div, so a caller can call update on the chart directly when it needs to.
Responsive options are the one prop that recreates the chart rather than updating it, and the example in the readme pairs a media query with an option and passes the result in:
const responsiveOptions = [
["screen and (max-width: 640px)", { showPoint: false }],
];
<ChartistGraph
data={{ labels: ["A", "B"], series: [[3, 7]] }}
responsiveOptions={responsiveOptions}
type="Line"
/>;The media query hides the points on narrow screens, and because Chartist only accepts responsive options at construction time, the component detaches and recreates the instance when this prop changes.
The events, the handle, and what the accessibility note says
Events are passed as a map keyed by the Chartist event names, and the example uses created and draw:
<ChartistGraph
data={{ series: [20, 30, 50] }}
listener={{
created: ({ svg }) => console.log(svg),
draw: (event) => console.log(event.type),
}}
type="Pie"
/>;The created handler receives the svg element, and the draw handler receives the event, so the type of the payload depends on the event, which is what the typed event handler map is for.
The imperative handle is the other way to reach the chart, and it is typed:
import { useRef } from "react";
import ChartistGraph, { type ChartistGraphHandle } from "react-chartist";
const chartRef = useRef<ChartistGraphHandle>(null);
<ChartistGraph data={{ series: [[1, 2, 3]] }} ref={chartRef} type="Bar" />;
chartRef.current?.chart?.update();The ref is a useRef of the ChartistGraphHandle type, and the handle exposes chart, the current Chartist instance, and element, the owned container div. The example then calls update on the chart instance through the optional chaining, so a caller can force a redraw when the data changed in a way the reference comparison did not catch.
The accessibility section is short and unusually blunt for a chart wrapper. Charts are visual summaries, not accessible replacements for their source data. The guidance is to add an accessible name with aria-label or aria-labelledby, and to provide the same information in text or a table when users need to inspect exact values.
That is worth taking seriously, because a chart component that silently produces an inaccessible graphic is a common failure. The component forwards aria attributes to the container, so the mechanism is there, but the component cannot invent the text alternative, and the note says so rather than pretending the component is sufficient.
The className prop is what makes the demo layout work, since the ct-major-twelfth class in the usage example is a Chartist class for a twelfth-width column, and the prop appends to the built-in ct-chart class rather than replacing it.
Eight breaking changes, and what they cost you
The migration section lists eight intentional breaking changes, and it is the most useful part of the readme for anyone coming from the 0.x line.
Chartist 1.5 replaces the old 0.10.1 API. React 18.2 and 19 are supported and older React versions are not. Types now come directly from Chartist and are selected by the type prop, which is the change that makes the discriminated union possible. The responsiveOptions prop uses the documented camel case name and recreates the chart when changed. Event listeners now update when the listener prop changes. Children are no longer cloned into the Chartist-owned container. The imperative ref is an object with chart and element rather than the old class component instance. And CommonJS users should destructure ChartistGraph from the require call.
Two of those are about the container rather than the API. Children are no longer cloned into the Chartist-owned container, and the ref exposes the element, which is the owned container div. Both point at the same design decision: in version 1 the component owns its container and does not let a parent inject arbitrary content into it, so what is inside the chart is determined by Chartist and not by the caller.
The CommonJS note is a practical one. The package publishes both formats, with the main field pointing at the CommonJS build, the module field at the ES module build, and an exports map that gives the types, the import condition and the require condition separately. A CommonJS consumer therefore has to destructure the named export rather than use the default import, because the default export is the ES module shape.
The chartist dependency and react are both peer dependencies, at 1.5.0 or newer and at 18.2 or 19 respectively, and the node engine requirement for consumers is 18.18 or newer. So the chart library is not bundled, which means the application controls the Chartist version and the React version, and the component's behaviour depends on both matching what it was written against.
That is the right arrangement for a wrapper, and it also means a version mismatch is the most likely upgrade failure.
The build, the check pipeline, and the packaging test
The development section is short. Node 20.19 or newer is required for the development toolchain, and the two commands are:
npm install
npm run checkThe check script is where the real quality story is, because it chains five things in order: lint, typecheck, test, build and a second typecheck of the package.
That last step is the unusual one, and the script it runs explains itself. The typecheck:package command compiles a separate file, test/package-consumer.mts, with tsc in ignoreConfig mode, strict mode, skipLibCheck, and an ES2022 target with NodeNext module resolution. In other words, it type checks the published package the way a consumer would consume it, from the outside, with its own compiler settings rather than the project's.
That catches a class of error that the project's own typecheck cannot, which is a mismatch between the types the source declares and the types the build actually publishes. A library that type checks internally but ships a broken declaration file passes every internal check and fails for every consumer, and the separate consumer test is the way to catch it before publishing.
The prepublishOnly hook runs the same check, so a publish cannot happen without it passing.
The build itself is tsup for the bundle and a separate tsc invocation against tsconfig.build.json for the declarations. The lint is Biome, with a check script and a write variant, and the tests run on Vitest, with a watch mode alongside the run mode.
The repository layout matches that. There is a src/ directory, a test/ directory, a docs/ directory, a biome.json, a tsup.config.ts, a vitest.config.ts, a tsconfig.json and a tsconfig.build.json, a .nvmrc, and a package-lock.json alongside a pinned packageManager of npm at a specific version.
The published files are dist, the changelog, the licence and the readme, and the package is marked sideEffects false, which tells a bundler it can drop unused imports. The esbuild version is pinned through an overrides entry, which is the mechanism for forcing a specific version of a transitive build dependency.
Editorial conclusion
react-chartist suits a project that has chosen Chartist and wants it in React with real types rather than a loosely typed wrapper, since the prop union and the directly imported Chartist types remove the guesswork from the chart data. It does not suit you if you need a charting library with its own rendering, because this component draws nothing itself and inherits everything about Chartist's output, including its accessibility limits. Before you adopt it, memoise your data and options objects, because the reference comparison will otherwise rebuild the chart on every render, and check that your Chartist version is 1.5 or newer, since the component is built against that generation and the 0.10 API is gone.
Frequently asked questions
How do I install react-chartist?
Run npm install react-chartist chartist. The chartist package is a peer dependency, so you install it yourself and control its version. You also need to import Chartist's stylesheet once in your application, for example with an import of chartist/dist/index.css.
Why does my react-chartist chart rebuild on every render?
Because data, options, responsiveOptions and listener are compared by reference, so an inline object literal produces a new reference each render. Memoise the values that do not need to change, with useMemo and an empty dependency array, so the chart is not recreated unnecessarily.
How do I access the Chartist instance in react-chartist?
Use a ref of the ChartistGraphHandle type. The handle exposes chart, the current Chartist instance, and element, the owned container div, so you can call update on the chart instance directly when you need to force a redraw.
What changed between react-chartist 0.x and 1.0?
Chartist 1.5 replaces the old 0.10.1 API, React 18.2 and 19 are supported and older versions are not, types come directly from Chartist and are selected by the type prop, the responsiveOptions prop uses the documented camel case name and recreates the chart when changed, event listeners update when the listener prop changes, children are no longer cloned into the Chartist owned container, and the imperative ref is an object with chart and element rather than a class component instance.
Is react-chartist accessible out of the box?
Not by itself. Charts are visual summaries rather than accessible replacements for the source data, so you should add an accessible name with aria-label or aria-labelledby and provide the same information as text or a table when users need exact values. The component does forward aria attributes and other div attributes to the chart container.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fraserxu-react-chartist)Was this useful?