Slope chart

Before and after on two axes: lines draw in, rank moves sit beside each value, and switching datasets slides every line to its new slope.

pnpm dlx shadcn@latest add @uiarc/slope-chart
Live · keyboard ready

Conversion by channel

Q1 to Q2 2026

Conversion rate by channel, Americas, Q1 to Q2. Email 3.1% to 4.6%, Direct 3.9% to 3.7%, Organic search 3.4% to 3.6%, Referral 2.6% to 3.2%, Paid search 2.8% to 2.5%, Paid social 1.9% to 1.3%.

Conversion rate by channel, Americas
ItemQ1Q2ChangeRank
Email3.1%4.6%+1.5 pts3 to 1
Direct3.9%3.7%−0.2 pts1 to 2
Organic search3.4%3.6%+0.2 pts2 to 3
Referral2.6%3.2%+0.6 pts5 to 4
Paid search2.8%2.5%−0.3 pts4 to 5
Paid social1.9%1.3%−0.6 pts6 to 6
  • Showing which items rose, which fell, and how the order changed between two periods.
  • Replacing a grouped bar chart when the change is the story.
  • Use line-chart for three or more moments.
  • Use bar-chart when only the latest values matter.
  • Avoid more than about ten items; labels start to crowd.

Installation

Add Slope chart with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/slope-chart

Adds the component and its local dependencies, and installs motion. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { SlopeChart } from "@/registry/components/slope-chart/slope-chart"; const channels = [  { key: "email", label: "Email", start: 3.1, end: 4.6 },  { key: "search", label: "Organic search", start: 3.4, end: 3.6 },  { key: "social", label: "Paid social", start: 1.9, end: 1.3 },]; export function Channels() {  return <SlopeChart data={channels} label="Conversion by channel" startLabel="Q1" endLabel="Q2" highlightKey="email" formatValue={value => `${value.toFixed(1)}%`} />;}

SlopeChart

Before and after for several items on two shared axes. Lines draw in on first view, labels spread apart to avoid overlaps with a hairline back to their point, rank moves sit beside each end value, and switching datasets slides every line to its new slope.

PropTypeDefaultDescription
dataRequiredSlopeItem[]–Items: { key, label, start, end }. Keys keep lines stable across datasets.
labelRequiredstring–What is measured. Names the chart, the summary, and the table.
startLabelRequiredstring–First column heading, such as "Q1".
endLabelRequiredstring–Second column heading, such as "Q2".
formatValue(value: number) => string–Formats values beside the points, in the tooltip, and in the table.
formatChange(change: number, item: SlopeItem) => string–Formats the change in the tooltip, such as "+1.5 pts".
heightnumber44px per itemPlot height in pixels.
highlightKeystring | nullnullThe item drawn in the first series hue, derived from the accent.
activeKeystring | null–Controlled item in focus. The others fade.
onActiveChange(key: string | null) => void–Called as the item in focus changes.
ranksbooleantrueRank movement beside each end value.
emptyLabelstring"No data"Message when there are no items.
refRef<HTMLElement>–Forwarded to the figure.
classNamestring–Extra class on the figure.
Tab
Focuses the chart and reads the top item by end value.
ArrowDownorArrowRight
Next item in end order.
ArrowUporArrowLeft
Previous item.
HomeorEnd
First or last item.
Escape
Clears the reading.
  • The chart is a focusable group with a roledescription and instructions; lines and labels are aria-hidden.
  • A polite live region reads both values, the change, and the rank move for the item in focus.
  • A visually hidden summary and a table list every item in end order.
  • Rank moves use an arrow glyph and a number, never colour alone.
  • On first view each line draws from start to end with a small stagger, and the end dots and labels arrive as it lands.
  • Switching datasets moves lines, dots, labels, and leader hairlines on the shared morph spring; rank numbers roll in the direction they moved.
  • The item in focus comes forward while the others fade; the tooltip glides to the middle of its line.
  • Reduced motion shows every line drawn and moves it immediately.
  • Label columns narrow below 440px and names truncate with an ellipsis; the full name stays in the tooltip and table.
  • Overlapping labels spread apart with a hairline to their point instead of colliding.
  • Touch reads the nearest line with a press and drag.
  • SVG lines and HTML labels animated with springs; around ten items render in a few dozen nodes.
  • One ResizeObserver measures the container.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Choose it for exactly two moments and three to ten items: before and after a launch, quarter over quarter, last year against this year.
  • Use highlightKey for the item the story is about.
  • Give the columns short headings; names sit on the left only, values on both sides.
  • For more than two moments use line-chart.

The full library index for assistants is at /llms.txt.