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.
datachartnew
Preview
Slope chart preview
Live · keyboard ready
Conversion rate by channel, Q1 to Q2. Organic search 38 to 29, Direct 24 to 27, Social 16 to 21, Newsletter 10 to 15, Referral 12 to 8.
| Item | Q1 | Q2 | Change | Rank |
|---|---|---|---|---|
| Organic search | 38 | 29 | −9 | 1 to 1 |
| Direct | 24 | 27 | +3 | 2 to 2 |
| Social | 16 | 21 | +5 | 3 to 3 |
| Newsletter | 10 | 15 | +5 | 5 to 4 |
| Referral | 12 | 8 | −4 | 4 to 5 |
When to use
- 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.
When not to use
- 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.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "slope-chart",
"type": "registry:ui",
"title": "Slope chart",
"description": "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.",
"categories": [
"data"
],
"dependencies": [
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/slope-chart/slope-chart.tsx",
"type": "registry:component",
"target": "@components/manicat/slope-chart/slope-chart.tsx"
},
{
"path": "registry/components/slope-chart/slope-chart.module.css",
"type": "registry:component",
"target": "@components/manicat/slope-chart/slope-chart.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/slope-chart",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/slope-chart",
"markdown": "https://uiarc.dev/components/slope-chart/markdown",
"tags": [
"data",
"chart",
"new"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { SlopeChart } from "@/components/manicat/slope-chart/slope-chart";
import type { SlopeItem } from "@/components/manicat/slope-chart/slope-chart";
const slopeData: SlopeItem[] = [
{ key: "search", label: "Organic search", start: 38, end: 29 },
{ key: "direct", label: "Direct", start: 24, end: 27 },
{ key: "referral", label: "Referral", start: 12, end: 8 },
{ key: "social", label: "Social", start: 16, end: 21 },
{ key: "newsletter", label: "Newsletter", start: 10, end: 15 },
];
export function SlopeChartExample() {
// Size containers have no width of their own: give the demo the whole row.
return <div className="w-full"><SlopeChart data={slopeData} label="Conversion rate by channel" startLabel="Q1" endLabel="Q2" ranks highlightKey="social" /></div>;
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Quarter over quarter Compare two moments per item and highlight the one the story is about.
Quarter over quarter
Compare two moments per item and highlight the one the story is about. preview
Live · keyboard ready
Conversion rate by channel, Q1 to Q2. Organic search 38 to 29, Direct 24 to 27, Social 16 to 21, Referral 12 to 8.
| Item | Q1 | Q2 | Change | Rank |
|---|---|---|---|---|
| Organic search | 38 | 29 | −9 | 1 to 1 |
| Direct | 24 | 27 | +3 | 2 to 2 |
| Social | 16 | 21 | +5 | 3 to 3 |
| Referral | 12 | 8 | −4 | 4 to 4 |
quarter-over-quarter-compare-two-moments-per-item-and-highlight-the-one-the-story-is-about-.tsxtsx
"use client";
import { SlopeChart } from "@/components/manicat/slope-chart/slope-chart";
import type { SlopeItem } from "@/components/manicat/slope-chart/slope-chart";
const channels: SlopeItem[] = [
{ key: "search", label: "Organic search", start: 38, end: 29 },
{ key: "direct", label: "Direct", start: 24, end: 27 },
{ key: "referral", label: "Referral", start: 12, end: 8 },
{ key: "social", label: "Social", start: 16, end: 21 },
];
export function SlopeChartChannelsExample() {
return <SlopeChart data={channels} label="Conversion rate by channel" startLabel="Q1" endLabel="Q2" ranks highlightKey="social" />;
}
Keyboard interactions
| Keys | Action |
|---|---|
| Tab | Focuses the chart and reads the top item by end value. |
| ArrowDown / ArrowRight | Next item in end order. |
| ArrowUp / ArrowLeft | Previous item. |
| Home / End | First or last item. |
| Escape | Clears the reading. |
Accessibility
- 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.
Motion
- 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.
Responsive behavior
- 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.
Performance
- 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
- 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.