Bump chart

Free · MIT

Rank-over-time lines that draw in with popping dots and names at the line ends; hovering or arrowing through periods and neighbours highlights one item and reads its change.

MarkdownOpen in v0
Open in v0

Installation

$ npx shadcn@latest add https://goodui.dev/r/bump-chart.json

The file lands in components/goodui/bump-chart.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/bump-chart works too.

Usage

import { BumpChart } from "@/components/goodui/bump-chart"

export function ServiceRanking() {
  return (
    <BumpChart
      title="Most booked services"
      periods={["May", "Jun", "Jul", "Aug", "Sep", "Oct"]}
      series={[
        { key: "balayage", label: "Balayage", ranks: [2, 1, 1, 1, 2, 1] },
        { key: "fade", label: "Skin fade", ranks: [1, 2, 3, 2, 1, 2] },
        { key: "manicure", label: "Gel manicure", ranks: [3, 3, 2, 3, 3, 4] },
        { key: "massage", label: "Deep tissue", ranks: [5, 4, 4, 5, 4, 3] },
        { key: "brows", label: "Brow lamination", ranks: [4, 5, 5, 4, 5, 5] },
      ]}
      rowHeight={40}
      labelWidth={104}
    />
  )
}

When to use

Use it for
  • Who is first, second, third over time: most booked services by month, staff by revenue by week, plans by sign-ups.
  • Stories about overtaking ("Balayage took the top spot in June") rather than exact values.
  • Up to about six items; names sit at the line ends so no legend is needed.
Reach for something else
  • Exact values matter: use line-chart.
  • Many items (10+): lines tangle; show a table with rank deltas instead.
  • A single period: a ranked horizontal bar-chart is the right form.
If you needUse
Monthly service rankingseries={[{ key, label, ranks }]} periods={months}
Tighter rowsrowHeight={34}
No #1…#n columnrankLabels={false}
Longer names at the endlabelWidth={120}

Examples

Weekly staff leaderboard

<BumpChart title="Staff by revenue" periods={weeks} series={staff} rowHeight={34} labelWidth={64} rankLabels={false} />

API reference

PropTypeDefault
series*BumpSeries[]—
periods*string[]—
title
Chart name for screen readers and the table caption.
React.ReactNode—
rowHeight
Vertical space per rank in px.
number36
labelWidth
Space reserved for the names at the end side, in px.
number88
rankLabels
"#1 … #n" on the start side.
booleantrue
strokeWidthnumber2.5
duration
Seconds one line takes to draw; lines stagger by 0.1 s.
number1.1
labels
Text for built-in controls and screen readers; pass translations here.
Partial<BumpChartLabels>—

Also accepts every <div> attribute.

Accessibility

TabFocuses the chart and highlights the first item at the last period.
ArrowLeft / ArrowRightPrevious or next period on the same item.
ArrowUp / ArrowDownThe item ranked just above or below at that period.
Home / EndFirst or last period.
EscapeClears the highlight.
  • The chart is role="group" with aria-roledescription="bump chart" and a summary ("Most booked services: rank of 5 items over 6 periods").
  • A screen-reader-only table lists each period with every item's rank.
  • The highlighted point is announced as "<item>: #2 in Jun, up 1" through a polite live region.
  • Names are printed at the line ends, so identity never relies on color; the rank axis is printed on the start side.
  • Built-in strings (rank, up, down, same, summary, point, column header, noData) go through labels.

Motion

  • On first view each line draws in from the first period with pathLength over duration (1.1 s, ease [0.65, 0, 0.35, 1]), 0.1 s apart; the dots pop on a spring (stiffness 420, damping 22) as the line reaches them and the names slide in at the end.
  • Hovering a line, a dot or arrowing to a point fades the other items to 22% over 0.2 s; the active dot grows by 1.5px.
  • Reduced motion: lines, dots and names appear at once.
  • Each line is one cubic path plus a transparent 16px hit path for easy hovering; dots sit in motion groups translated once per layout.

Notes for AI

Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.

  • ranks are 1-based and one per period; the number of rows is the largest rank (at least the number of series).
  • Lines are smooth S-curves between periods; there is no value axis, only rank.
  • Colors default to --chart-1 … --chart-5 by series order; keep one color per entity across charts.
  • The chart sizes its own height (rows × rowHeight + axis); the width follows the parent, with labelWidth reserved for names.

FAQ

Can an item be missing in some periods?

Leave the rank undefined at that index: the line skips the point. Ranks arrays shorter than periods simply end early.

Can I show values on hover?

The tooltip shows the rank and the change; attach values to your labels or use line-chart for values.

Related