Line Chart
A composable line chart with tooltips, markers, and hover interactions
Preview
Installation
pnpm dlx shadcn@latest add @bklit/line-chartUsage
Build charts by composing components. See the charts gallery for interactive examples.
import { LineChart, Line, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts";
const data = [
{ date: new Date("2025-01-01"), users: 1200 },
{ date: new Date("2025-01-02"), users: 1350 },
// ...
];
export default function SimpleChart() {
return (
<LineChart data={data}>
<Grid horizontal />
<Line dataKey="users" />
<XAxis />
<ChartTooltip />
</LineChart>
);
}Components
LineChart
The root component that provides context to all children.
| Prop | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | required | Array of data points |
xDataKey | string | "date" | Key in data for x-axis values |
margin | Partial<Margin> | { top: 40, right: 40, bottom: 40, left: 40 } | Chart margins |
animationDuration | number | 1100 | Clip-reveal duration in ms (cubic-bezier(0.85, 0, 0.15, 1)) |
status | "loading" | "ready" | "ready" | Loading ↔ ready choreography on one chart instance |
loadingLabel | string | — | Centered shimmer label while status="loading" ("" hides it) |
yDomainTween | boolean | true | Animate y-domain when status or target domain changes |
yDomainTweenDuration | number | 500 | Y-domain tween duration in ms |
xDomain | [Date, Date] | — | Visible x-range for brush zoom |
xDomainSlotCount | number | — | Full dataset length for x-scale padding when xDomain is set |
tweenYDomainOnXDomainChange | boolean | false | Tween y-domain when the brush changes the visible x-range |
aspectRatio | string | "2 / 1" | CSS aspect ratio |
className | string | "" | Additional CSS class |
style | CSSProperties | — | Inline container styles (e.g. fixed height for a brush strip) |
Line
Renders a line on the chart.
| Prop | Type | Default | Description |
|---|---|---|---|
dataKey | string | required | Key in data for y values |
yAxisId | string | number | "left" | Y-scale group for biaxial charts (pair with YAxis) |
stroke | string | var(--chart-line-primary) | Line color |
strokeWidth | number | 2.5 | Line width |
curve | CurveFactory | curveNatural | D3 curve function |
animate | boolean | true | Enable grow animation |
fadeEdges | boolean | true | Fade line at edges |
showHighlight | boolean | true | Show highlight on hover |
showMarkers | boolean | false | Render scatter-style ring markers at each point |
loadingStroke | string | var(--foreground) | Pulse stroke color while chart is loading |
loadingStrokeOpacity | number | 0.5 | Pulse stroke opacity while chart is loading |
markers | SeriesPointMarkerStyle | — | Marker styling (same options as Scatter) |
Grid
Renders grid lines.
| Prop | Type | Default | Description |
|---|---|---|---|
horizontal | boolean | true | Show horizontal lines |
vertical | boolean | false | Show vertical lines |
numTicksRows | number | 5 | Number of horizontal lines |
numTicksColumns | number | 10 | Number of vertical lines |
stroke | string | var(--chart-grid) | Line color while ready |
loadingStroke | string | — | Grid stroke while loading chrome is active |
strokeDasharray | string | "4,4" | Dash pattern |
highlightRowValues | number[] | — | Draw emphasized horizontal lines at specific y values (e.g. [0] for break-even) |
highlightRowStroke | string | var(--chart-foreground-muted) | Stroke for highlighted rows |
highlightRowStrokeOpacity | number | 1 | Opacity for highlighted rows |
highlightRowStrokeWidth | number | 1 | Width for highlighted rows |
highlightRowStrokeDasharray | string | "0" | Dash pattern for highlighted rows ("0" = solid) |
shimmer | boolean | false | Animate a shimmer band across horizontal grid lines |
shimmerStroke | string | color-mix(…) on --foreground at 68% | Shimmer band color and opacity |
shimmerLength | number | 140 | Shimmer band width in pixels |
shimmerSpeed | number | 1 | Shimmer speed multiplier when sync is off (higher = faster) |
shimmerSync | boolean | false | Match shimmer timing to the line pulse (2.2s cycle + 280ms pause) |
Background
Pattern fill for the plot area when you omit Grid. Fades in after the series reveal on time-series charts. See the Background utility for presets (diagonal, dots, cross, …), edge fade, and opacity — and the Pattern Background examples on the line chart gallery.
XAxis
Renders x-axis labels that fade when the crosshair passes.
| Prop | Type | Default | Description |
|---|---|---|---|
numTicks | number | 6 | Number of tick labels |
tickerHalfWidth | number | 50 | Fade radius for labels |
tickMode | "domain" | "data" | "domain" | "domain" for evenly spaced ticks; "data" for one label per row |
ChartTooltip
Renders the tooltip with crosshair, dots, and content box.
| Prop | Type | Default | Description |
|---|---|---|---|
showDatePill | boolean | true | Show animated date ticker |
showCrosshair | boolean | true | Show vertical crosshair |
showDots | boolean | true | Show dots on lines |
indicatorColor | string | (point) => string | — | Crosshair and dot color; use a function for value-based colors |
indicatorDasharray | string | — | Dash pattern for the crosshair (e.g. "4,4") |
indicatorFadeEdges | "both" | "top" | "bottom" | "none" | "both" | Vertical crosshair fade |
indicatorFadeLength | number | 10 | Fade size (% of height) |
matchCrosshair | boolean | false | Panel uses crosshair spring when true |
damping | number | 20 | Panel follow when matchCrosshair={false}; 0 = instant |
content | (props) => ReactNode | - | Custom content renderer |
rows | (point) => TooltipRow[] | - | Custom row generator |
Dual Y axes (biaxial)
Pair yAxisId on each Line with matching YAxis components. Increase margin.left and margin.right so labels fit. See Y Axis and the Left and right Y axes examples on the line chart gallery.
Profit/Loss line
For a single series that crosses zero, use ProfitLossLine inside LineChart. Pair it with a hidden Line (same dataKey) so the chart registers the series for the y-domain and tooltip. When any value is negative, the y-axis automatically includes the full data extent instead of anchoring at zero.
Highlight the break-even baseline with Grid highlightRowValues={[0]}:
<Grid
highlightRowValues={[0]}
highlightRowStroke="var(--foreground)"
highlightRowStrokeOpacity={0.35}
horizontal
/>See the Profit/Loss Line docs and the line chart gallery (Profit/Loss example).
Brush zoom
Use ChartBrushLayout and ChartBrush the same way as on AreaChart. See the Brush utility docs for full API reference. The brush strip typically shows simplified Line series; the main chart receives xDomain, xDomainSlotCount, and tweenYDomainOnXDomainChange for live zoom and y-domain tweening.
Preview
Open in Studio<ChartBrushLayoutdata={data}enabledheight={72}brushStrip={(layout) => ( <LineChart data={data} animationDuration={0} status="ready"> <Line dataKey="value" animate={false} /> <ChartBrush initialSelection={layout.brushSelection ?? undefined} onSelectionChange={layout.onBrushSelectionChange} /> </LineChart>)}>{(layout) => ( <LineChart data={data} xDomain={layout.xDomain} xDomainSlotCount={layout.xDomainSlotCount} tweenYDomainOnXDomainChange yDomainTween > <Grid horizontal /> <Line dataKey="value" /> <XAxis /> <ChartTooltip /> </LineChart>)}</ChartBrushLayout>Open Studio with brush enabled to tune strip height, blur, and selection pattern.
Loading state
Drive loading and ready from your data layer with a single LineChart — one Grid, one Line, no component swap. Set status="loading" while fetching; switch to "ready" when data resolves.
Loading → ready: pulse loop on skeleton data → pulse finishes its grow, then flows out right → loading label drifts down 30px, blurs, and fades → grid y-domain tween (500ms) → clip-path reveal (cubic-bezier(0.85, 0, 0.15, 1)) → interaction enabled.
Ready → loading: ready line conceals to the right → grid y-domain tween → pulse loop and shimmer resume.
Pair Grid stroke / loadingStroke with shimmer props. Pair Line loadingStroke props. Use loadingLabel on LineChart for centered shimmer text via @ncdai/shimmering-text.
Preview
Open in Studioconst [status, setStatus] = useState<"loading" | "ready">("loading");<LineChartdata={data}status={status}loadingLabel="Loading revenue…"yDomainTween><Grid horizontal loadingStroke="color-mix(in oklch, var(--chart-grid) 50%, transparent)" shimmer shimmerSync stroke="var(--chart-grid)"/><Line dataKey="revenue" fadeEdges loadingStroke="var(--foreground)" loadingStrokeOpacity={0.5} stroke="var(--chart-line-primary)"/></LineChart>Toggle Loading / Ready in the preview to replay the transition. When target data spans a different y-range than the skeleton, yDomainTween morphs the scale before the line reveals.
Studio
Open Studio in loading mode and set State to Loading. The components tree exposes Grid, Label, and Line:
| Layer | Controls |
|---|---|
| Grid | Grid and shimmer color pickers, shimmer toggle, band length, Animation (sync with line, speed when unsynced) |
| Label | Shimmer label text |
| Line | Pulse stroke color and opacity |
Data and animation panels stay collapsed in loading mode; scramble data is disabled. See the line chart gallery (Loading example).
Add @ncdai to your registry config (https://chanhdai.com/r/{name}.json) — installing @bklit/line-chart pulls in @ncdai/shimmering-text automatically.
Dashed tail
Set dashFromIndex on Line to draw a solid stroke through one data point, then a dashed segment through the end of the series. Useful when the final period is still in progress (e.g. yesterday → today).
dashFromIndex is inclusive — dashing starts at that row and continues through the last point. The dashed segment follows the same curved path as the solid stroke and respects fadeEdges.
| Prop | Type | Default | Description |
|---|---|---|---|
dashFromIndex | number | — | Inclusive data index where the dashed tail begins |
dashArray | string | "6,4" | SVG stroke-dasharray pattern for the tail segment |
<Line
dataKey="visitors"
dashFromIndex={5}
dashArray="6,4"
stroke="var(--chart-line-primary)"
/>Markers
Add markers to annotate specific dates on the chart:
import { LineChart, Line, ChartTooltip, ChartMarkers, MarkerTooltipContent, useActiveMarkers, type ChartMarker } from "@bklitui/ui/charts";
const markers: ChartMarker[] = [
{
date: new Date("2025-01-05"),
icon: "🚀",
title: "v1.2.0 Released",
description: "New chart animations",
},
{
date: new Date("2025-01-05"), // Same day - will stack!
icon: "🐛",
title: "Bug Fix",
description: "Fixed tooltip positioning",
},
];
function MyChart({ data }) {
return (
<LineChart data={data}>
<Line dataKey="users" />
<ChartMarkers items={markers} />
<ChartTooltip>
<MarkerContent markers={markers} />
</ChartTooltip>
</LineChart>
);
}
// Use the hook to get markers for the hovered date
function MarkerContent({ markers }) {
const activeMarkers = useActiveMarkers(markers);
if (activeMarkers.length === 0) return null;
return <MarkerTooltipContent markers={activeMarkers} />;
}ChartMarker Interface
interface ChartMarker {
date: Date; // Date for marker position
icon: React.ReactNode; // Icon (emoji or component)
title: string; // Tooltip title
description?: string; // Optional description
content?: React.ReactNode; // Custom tooltip content
color?: string; // Background color override
onClick?: () => void; // Click handler
href?: string; // URL to navigate to
target?: "_blank" | "_self"; // Link target
}ChartMarkers Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | ChartMarker[] | required | Array of markers |
size | number | 28 | Marker circle size |
showLines | boolean | true | Show vertical guide lines |
animate | boolean | true | Animate markers on entrance |
Segment Selection
Add click-drag and touch segment selection with composable components. The line highlight automatically shows the selected path segment.
Basic Usage
Click and drag (or two-finger touch on mobile) to select a range:
import { LineChart, Line, Grid, XAxis, ChartTooltip, SegmentBackground, SegmentLineFrom, SegmentLineTo } from "@bklitui/ui/charts";
<LineChart data={data}>
<Grid horizontal />
<Line dataKey="users" />
<SegmentBackground />
<SegmentLineFrom />
<SegmentLineTo />
<XAxis />
<ChartTooltip />
</LineChart>Use SegmentBackground, SegmentLineFrom, and SegmentLineTo independently — you do not need all three. Boundary lines support variant="dashed" | "solid" | "gradient".
Reading Selection Data
Use the useChart hook inside a child component to read the active selection:
import { useChart } from "@bklitui/ui/charts";
function SelectionStats({ onSelectionChange }) {
const { selection, data, xAccessor } = useChart();
useEffect(() => {
if (!selection?.active) {
onSelectionChange(null);
return;
}
const startPoint = data[selection.startIndex];
const endPoint = data[selection.endIndex];
// Compute and report stats...
onSelectionChange({ startPoint, endPoint });
}, [selection, data, xAccessor, onSelectionChange]);
return null;
}SegmentBackground
| Prop | Type | Default | Description |
|---|---|---|---|
fill | string | var(--chart-segment-background) | Fill color for the selected region |
SegmentLineFrom / SegmentLineTo
| Prop | Type | Default | Description |
|---|---|---|---|
stroke | string | var(--chart-segment-line) | Line color |
strokeWidth | number | 1 | Line width |
variant | "dashed" | "solid" | "gradient" | "dashed" | Line style |
Theming
The chart uses CSS variables for theming. Define these in your CSS:
:root {
--chart-background: oklch(1 0 0);
--chart-foreground: oklch(0.145 0.004 285);
--chart-foreground-muted: oklch(0.55 0.014 260);
--chart-line-primary: oklch(0.623 0.214 255);
--chart-line-secondary: oklch(0.705 0.015 265);
--chart-crosshair: oklch(0.4 0.1828 274.34);
--chart-grid: oklch(0.9 0 0);
--chart-tooltip-foreground: oklch(0.985 0 0);
--chart-tooltip-muted: oklch(0.65 0.01 260);
--chart-marker-background: oklch(0.97 0.005 260);
--chart-marker-border: oklch(0.85 0.01 260);
--chart-marker-foreground: oklch(0.3 0.01 260);
--chart-marker-badge-background: oklch(0 0 0);
--chart-marker-badge-foreground: oklch(1 0 0);
--chart-segment-background: oklch(0.5 0 0 / 0.06);
--chart-segment-line: oklch(0.5 0 0 / 0.25);
}
.dark {
--chart-background: oklch(0.145 0 0);
--chart-foreground: oklch(0.45 0 0);
--chart-crosshair: oklch(0.45 0 0);
--chart-grid: oklch(0.25 0 0);
--chart-marker-background: oklch(0.25 0.01 260);
--chart-marker-border: oklch(0.4 0.01 260);
--chart-marker-foreground: oklch(0.9 0 0);
--chart-marker-badge-background: oklch(1 0 0);
--chart-marker-badge-foreground: oklch(0.15 0 0);
--chart-segment-background: oklch(1 0 0 / 0.06);
--chart-segment-line: oklch(1 0 0 / 0.25);
}Dependencies
This component requires the following packages:
pnpm add @visx/shape @visx/curve @visx/scale @visx/gradient @visx/responsive @visx/event @visx/grid d3-array motion react-use-measure