If you have ever tried to build a chart with raw D3, you know the deal. D3 is gorgeous and almost limitless, but it asks you to think in scales, axes, selections, and enter/update/exit cycles before you can draw a single line. billboard.js (npm: billboard.js) takes the other approach. It hands you all of D3's drawing power behind a single declarative configuration object, so describing a chart feels less like writing a rendering engine and more like filling out a form. You say "here is my data, make it a line chart, turn on zoom," and a fully interactive, themeable, animated chart appears.
The library comes from NAVER, the Korean tech company, and it is the spiritual successor to the much-loved but now-dormant C3.js. It is written in TypeScript, ships strong type definitions, and sits at roughly 47,000 weekly downloads with around 6,000 stars on GitHub. It is MIT licensed and very actively maintained, with the latest 4.0 release landing in June 2026. If you want a chart library that covers everything from a simple line graph to candlesticks, treemaps, and sankey diagrams, without making you reinvent D3 every time, billboard.js is squarely aimed at you.
What You Get in the Box
The headline feature is breadth. billboard.js supports a genuinely large catalog of chart types out of the box: line, area, bar, pie, donut, gauge, scatter, bubble, radar, polar, candlestick, treemap, funnel, and sankey, among others. You can also mix types within a single chart, so one series can render as bars while another rides on top as a line.
Interactivity comes baked in rather than bolted on. Zoom, tooltips, a draggable subchart for navigation, point selection, and region highlighting are all configuration flags rather than features you assemble yourself. The instance API lets you push and pull data after the chart exists, so live dashboards and streaming feeds are first-class citizens.
Then there is the visual side. The library ships several CSS themes, including dark, insight, graph, modern, and datalab. Swapping themes is as simple as loading a different stylesheet, and because everything renders to real DOM nodes by default, you can style it further with plain CSS. The whole thing is built on granular D3 v6+ modules, so you are standing on a well-tested foundation rather than a bespoke renderer.
The most interesting recent addition, though, is performance related, and it deserves its own section later.
Getting It Installed
billboard.js is a normal npm package. Install it with whichever package manager you prefer.
npm install billboard.js
yarn add billboard.js
One thing to remember: the library ships a stylesheet, and the chart will look broken without it. Import the base CSS, or one of the bundled themes, alongside the JavaScript. If you are working in React, NAVER also publishes an official wrapper, @billboard.js/react, but the core library works perfectly well with a small amount of glue code, which we will cover.
Drawing Your First Chart
The entire library revolves around a single function: bb.generate. You give it an object describing where to mount, what data to show, and how to behave, and it returns a chart instance you can command later.
import bb, { line } from "billboard.js";
import "billboard.js/dist/billboard.css";
const chart = bb.generate({
bindto: "#chart",
data: {
columns: [
["downloads", 30, 200, 100, 400, 150, 250],
["stars", 130, 100, 140, 35, 110, 50],
],
type: line(),
},
});
The bindto option takes a CSS selector or an element, and the chart mounts itself there. The data.columns format is the signature billboard.js style: each array starts with the series name and is followed by its values. The type is set to line(), which is the modular form of the chart type. You import only the chart types you actually use, which keeps your bundle lean. If you would rather not bother with imports while prototyping, you can pass the string "line" instead and let the all-in-one build sort it out.
That snippet alone gives you a two-series, animated, interactive line chart with hover tooltips. No scales, no axes, no selections.
Feeding the Chart Real Data
Hardcoded columns are fine for a demo, but real charts pull from somewhere. billboard.js accepts data in several shapes, and the JSON form is usually the most natural when your data comes from an API.
import bb, { bar } from "billboard.js";
const chart = bb.generate({
bindto: "#chart",
data: {
json: [
{ month: "Jan", web: 200, mobile: 120 },
{ month: "Feb", web: 320, mobile: 180 },
{ month: "Mar", web: 280, mobile: 240 },
],
keys: {
x: "month",
value: ["web", "mobile"],
},
type: bar(),
},
axis: {
x: { type: "category" },
},
});
Here the keys option tells billboard.js which property is the x-axis and which properties are the value series. The axis.x.type of "category" makes the months render as discrete buckets rather than a continuous numeric scale. You can also load straight from a URL with data.url, pointing it at a JSON or CSV endpoint, which is handy when the chart should fetch its own data.
Making It Talk Back
A static chart is a picture. An interactive one is a tool. billboard.js exposes interactions as plain options, so adding zoom, a navigation subchart, and richer tooltips is a matter of flipping switches.
import bb, { spline, zoom } from "billboard.js";
const chart = bb.generate({
bindto: "#chart",
data: {
columns: [["revenue", 30, 200, 100, 400, 150, 250, 380, 90]],
type: spline(),
},
zoom: {
enabled: zoom(),
type: "drag",
},
subchart: {
show: true,
},
tooltip: {
grouped: false,
},
});
This chart can be dragged to zoom into a region, shows a miniature subchart underneath for orienting yourself in the full dataset, and gives each series its own tooltip rather than a grouped one. Each of those used to be a meaningful chunk of D3 work; here they are three short option blocks. The spline() type, by the way, is just a smoothed line, one of the small touches that make the library feel finished.
Living in React
The config-object approach maps cleanly onto a React effect. The pattern is to keep the chart instance in a ref, generate it on mount, and destroy it on unmount so you do not leak DOM or listeners.
import { useEffect, useRef } from "react";
import bb, { Chart } from "billboard.js";
import { bar } from "billboard.js";
import "billboard.js/dist/billboard.css";
export function SalesChart({ columns }: { columns: [string, ...number[]][] }) {
const elementRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<Chart | null>(null);
useEffect(() => {
if (!elementRef.current) return;
chartRef.current = bb.generate({
bindto: elementRef.current,
data: { columns, type: bar() },
});
return () => {
chartRef.current?.destroy();
chartRef.current = null;
};
}, []);
useEffect(() => {
chartRef.current?.load({ columns });
}, [columns]);
return <div ref={elementRef} />;
}
Notice the second effect. Instead of tearing down and rebuilding the chart whenever the data changes, it calls chart.load() to animate the new values into place. That is the key insight for using imperative chart libraries in a declarative framework: create once, command thereafter. If you would rather not write this yourself, the official @billboard.js/react package wraps exactly this lifecycle into a component.
The Canvas Trick in Version 4
This is the big news in the 4.0 release from June 2026, and it solves a problem every SVG chart library eventually hits. By default billboard.js renders to SVG, which means one DOM node per data point. That is lovely for styling and accessibility, but it falls apart when you ask the browser to manage tens of thousands of nodes at once. The page chugs, scrolling stutters, and tooltips lag.
Version 4 introduces an opt-in canvas rendering mode aimed precisely at high-density axis charts. Instead of a DOM node per point, the whole chart is painted onto a single <canvas> element. NAVER's benchmark suite reports the change as 94.3% faster overall, which is the kind of number that turns an unusable dashboard into a smooth one.
import bb, { line, canvas } from "billboard.js/canvas";
const chart = bb.generate({
render: {
mode: canvas(),
},
bindto: "#chart",
data: {
columns: [["pings", /* thousands of values */]],
type: line(),
},
});
Two things make this painless. First, it is opt-in, so existing SVG charts keep behaving exactly as before; you only reach for canvas when the data volume demands it. Second, you import from the dedicated billboard.js/canvas entry point and set render.mode to canvas(). The rest of your config stays the same. Worth knowing: canvas mode requires the browser's Canvas 2D API, and its feature parity is scoped to the high-density axis chart types where the performance win actually matters, rather than mirroring every SVG flourish. For dense line, area, scatter, and bar charts, though, it is a quiet superpower.
Reaching for the Instance API
Because bb.generate returns a live object, you are never stuck with the chart you first drew. The instance API is rich: load() and unload() add and remove series, flow() streams new points in from the right edge for real-time feeds, resize() handles layout changes, export() produces a PNG of the current view, and focus() and defocus() let you spotlight a series interactively.
chart.flow({
columns: [["pings", 180, 220, 160]],
duration: 600,
});
const pngDataUrl = chart.export("image/png");
The flow call slides three new data points in with a 600-millisecond animation, perfect for a metrics dashboard ticking forward. The export call gives you a data URL you can drop into a download link or an email. There is also a plugin system for extras like a table view of the underlying data, a Stanford diagram, and text-overlap handling, so the library grows with your needs instead of boxing you in.
When billboard.js Is the Right Call
billboard.js earns its place when you want serious chart variety and D3-grade flexibility without writing D3 by hand. It is the natural landing spot for anyone migrating off C3.js, and the official migration guide makes that move gentle. It carries D3 as a runtime dependency, so it is heavier at baseline than a micro chart library, and you will want to lean on ESM imports and tree-shaking to keep the bundle trim rather than pulling the whole all-in-one build. The configuration style is also less component-driven than something like Recharts, so React purists may prefer the instance-API rhythm of create-once-command-after.
But the trade is a fair one. You get a TypeScript-first, actively maintained, MIT-licensed library backed by NAVER, covering nearly every chart type you could name, with built-in interactions, drop-in theming, an official React wrapper, and now a canvas rendering mode that makes dense datasets fly. If your charts have outgrown the simple stuff but you would rather not spend your week wrestling scales and axes, billboard.js gives you the power and keeps the homework. Your data gets to look good, and you get to go home on time.