motion-chart

Bar and line charts written in HTML. Put the numbers in an attribute or wrap a table. Bars grow and lines draw in when the chart scrolls into view. When the data changes, every bar and point springs to its new value.

Preview

Hover a bar, or press New data several times quickly. The bars turn around from wherever they are.

<motion-chart
  values="12, 19, 8, 24, 16, 21, 14"
  labels="Mon, Tue, Wed, Thu, Fri, Sat, Sun"
  label="Orders per day"
></motion-chart>

<button id="new-data">New data</button>

Set values from JavaScript to change the data. The chart springs to the new numbers:

const chart = document.querySelector('motion-chart')
const datasets = [
  '12, 19, 8, 24, 16, 21, 14',
  '20, 9, 17, 10, 27, 25, 14',
  '8, 24, 15, 19, 11, 28, 22',
]
let step = 0

document.querySelector('#new-data').addEventListener('click', () => {
  step = (step + 1) % datasets.length
  chart.values = datasets[step]
})

Quick start

Save this as index.html and open it in a browser. No install, no build step.

<!doctype html>
<html>
  <head>
    <script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-chart.js/+esm"></script>
  </head>
  <body>
    <motion-chart
      values="12, 19, 8, 24, 16"
      labels="Mon, Tue, Wed, Thu, Fri"
      label="Orders per day"
    ></motion-chart>
  </body>
</html>

In a project with a bundler, install the package and import the component once:

npm install motion-components
import 'motion-components/motion-chart'

Give the chart its data

There are three ways. Pick the one that fits where your data comes from.

1. Numbers in attributes

Separate the numbers with commas. For several series, separate the series with ; and name them in series.

<motion-chart
  values="42, 55, 61, 48; 30, 38, 52, 60"
  labels="Q1, Q2, Q3, Q4"
  series="2025, 2026"
  format="currency:EUR"
></motion-chart>

2. A table

Wrap a normal <table>. The first column holds the labels, and every other column is one series. The header row names the series. Screen readers still read the table, and the chart updates when a cell changes.

MonthVisitorsSignups
Jan1200310
Feb1850420
Mar1600530
Apr2400610
May2900780
Jun3300900
<motion-chart type="line" label="Visitors and signups">
  <table>
    <tr><th>Month</th><th>Visitors</th><th>Signups</th></tr>
    <tr><td>Jan</td><td>1200</td><td>310</td></tr>
    <tr><td>Feb</td><td>1850</td><td>420</td></tr>
    <tr><td>Mar</td><td>1600</td><td>530</td></tr>
    <tr><td>Apr</td><td>2400</td><td>610</td></tr>
    <tr><td>May</td><td>2900</td><td>780</td></tr>
    <tr><td>Jun</td><td>3300</td><td>900</td></tr>
  </table>
</motion-chart>

How a cell becomes a number:

3. From JavaScript

Set the data property. Every time you set it, the chart springs to the new values.

const chart = document.querySelector('motion-chart')

chart.data = {
  labels: ['Jan', 'Feb', 'Mar'],
  series: [
    { name: 'Visitors', values: [1200, 1850, 1600] },
    { name: 'Signups', values: [310, 420, 530] },
  ],
}

For parts of a whole, use motion-pie, which draws pie and donut charts.

Live data

Change values, data or a table cell, and the chart moves to the new numbers. This line gets a new value every 1.5 seconds.

<motion-chart id="live" type="line" values="40, 42, 45, 43, 48, 52, 50, 55"></motion-chart>

<script type="module">
  const chart = document.querySelector('#live')

  setInterval(() => {
    const values = chart.values.split(',').map(Number)
    const next = values.at(-1) + Math.round(Math.random() * 10 - 4)
    chart.values = [...values.slice(1), next].join(', ')
  }, 1500)
</script>

When the range changes, the scale and its grid lines spring to the new range. Old grid lines fade out and new ones fade in. Set animate-scale="false" to switch the scale at once.

<motion-chart type="line" values="40, 42, 45, 43" animate-scale="false"></motion-chart>

Number format

format formats the axis, the tooltip and the screen reader text in the reader's language.

format 12900 shows as
(empty) 12,900
compact 12.9K
currency:EUR €12,900
percent 12,900%
unit:kilometer 12,900 km

percent adds a percent sign. It does not multiply by 100, so write 12 for 12%.

More patterns, such as a period switcher, a chart from a form and data from a JSON file, are on the Chart Recipes page.

Frameworks

Write the data into attributes, or wrap a table.

<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-chart.js/+esm"></script>

<motion-chart values="12, 19, 8, 24" labels="Mon, Tue, Wed, Thu"></motion-chart>

React 19 sets data as a property, so pass the object from state. The chart springs to every new value.

import 'motion-components/motion-chart'

export function SalesChart({ months, sales }) {
  const data = { labels: months, series: [{ name: 'Sales', values: sales }] }

  return <motion-chart type="line" format="compact" data={data}></motion-chart>
}

Tell Vue that motion-* tags are custom elements (see Getting started), then bind :data.

<script setup>
import 'motion-components/motion-chart'

const props = defineProps(['months', 'sales'])
</script>

<template>
  <motion-chart
    type="line"
    format="compact"
    :data="{ labels: props.months, series: [{ name: 'Sales', values: props.sales }] }"
  ></motion-chart>
</template>

Colors and size

The eight series colors are chosen to stay apart for color-blind readers, in light and dark mode. Override them with --mc-chart-1 to --mc-chart-8. Text and gridlines follow the text color.

<motion-chart
  values="8, 14, 11, 19"
  labels="North, East, South, West"
  style="--mc-chart-1: #16a34a; --mc-chart-height: 12rem"
></motion-chart>

Properties

Attribute Property Type Default Description
type type string 'bar' 'bar' or 'line'
values values string '' Comma-separated numbers. Separate several series with ;
labels labels string '' Comma-separated labels for values
series series string '' Comma-separated series names for values
data data object null Property only: { labels, series: [{ name, values }] }. Takes precedence over a table and values
format format string '' 'compact', 'percent', 'currency:EUR' or 'unit:kilometer'
min min number auto Lowest value on the axis. Defaults to the lower of 0 and the smallest value
max max number auto Highest value on the axis. Defaults to the largest value, rounded up
animate-scale animateScale boolean true Springs the scale and grid lines to the new range when the data changes. Set animate-scale="false" to switch the scale at once
label label string '' Accessible name of the chart
trigger trigger string 'view' When the entrance runs: 'view' or 'mount'
duration duration number 0.8 Spring duration in seconds
bounce bounce number 0.25 Spring bounciness (0 = no overshoot)
interval interval number 0.04 Seconds between one bar group and the next on entrance
tooltip tooltip boolean true Set tooltip="false" to hide the tooltip
legend legend boolean true Shown for two or more series. Set legend="false" to hide it

CSS custom properties

Property Type Default Description
--mc-chart-height length 16rem Height of the chart, including the legend
--mc-chart-1 … --mc-chart-8 color blue, orange, aqua, yellow, magenta, green, violet, red Series colors, in order
--mc-chart-surface color Canvas Background of the tooltip

JavaScript API

replay() runs the entrance again. Like every animated component, it also has play(), pause(), finish(), cancel(), playState and the motion-start and motion-finish events. See the JavaScript API.

Accessibility

Screen readers get the data as a table: your own table when you wrap one, or a generated one for values and data. Give the chart a label. Press Tab to focus the chart, then ← and → to move between points, Home and End for the first and last, and Esc to close the tooltip. Each point is announced with its values. With prefers-reduced-motion, the chart shows its final state and changes values at once.