LineChart
One or more trends over an ordered x axis, with a shared crosshair tooltip listing every series at the hovered position. The default for change over time — and with `guides="false"` and a small `height`, the inline sparkline in a stat tile or table cell.
<UiLineChart :data="..." :categoryKey="..." :series="..." :ariaLabel="..." />Needs the charts module
This component ships in @jgastager/bundy-ui/charts, a separate module so projects that never draw a chart don't carry the paint classes these need. Nothing extra to install — just add the module entry. See Charts.
Data
requireddata keeps rows in the wide shape your API already returns — one object per x position, one field per series. No reshaping, no melting into long form.
- Desktop
- Mobile
data stays in the wide shape an API returns ({ month: "Jan", desktop: 120, mobile: 80 }); the component folds it into the long rows the marks want. Rows whose value isn't a finite number are dropped rather than zeroed, so a gap in the data reads as a gap.
The hover fade is a CSS transition over the library's own 250ms duration, so it costs nothing and honours prefers-reduced-motion. There's no animate prop and nothing tweens on a data change — the marks are plain SVG elements Vue patches directly.
Category key
requiredcategoryKey sets which field on each row is its x value. Everything else in the row is a candidate series.
Series
requiredseries is field names for the quick case, or { key, label, color } when the legend needs better words than your column names. Order assigns the palette slots, so keep it stable across renders.
- desktop
- mobile
- Desktop
- Mobile
Colors are the library's own semantic ones — primary, secondary, success, warning, error, info, pending, neutral — and any shade of them, primary-400 through primary-950, exactly as the bg-* utilities spell them. Set color per series (or per item) to pin one; leave it unset to take the next slot of the default sequence. neutral and secondary are never handed out automatically, so an unlabelled series never lands on them.
ARIA label
requiredariaLabel sets what the chart shows, in one sentence. Required — an unlabelled chart is an unreadable one, and it's the only thing a screen reader has to go on.
ARIA description
ariaDescription is the detail the label shouldn't carry — the trend, the outlier, the thing a sighted reader gets from the shape.
Categories become an evenly-spaced point scale — dates included. Twelve monthly buckets are drawn evenly regardless of month length, which is right for buckets and wrong for irregular timestamps. When the gaps carry meaning, draw it yourself in a ChartFrame with a linearScale over epoch milliseconds on both axes.
Curve
curve says what happens between two samples. linear joins them and claims nothing else. smooth is monotone cubic — it rounds corners without bulging past a value the data never contained. The three step variants say the value held and then jumped, which is what a price, a setting or a plan tier actually did: step-after holds until the next sample, step-before jumps first, step splits the difference. Reach for a step curve whenever “between two samples” has no meaning.
Points
points marks the real samples. Worth turning on whenever the series is sparse enough that a reader might mistake the line for continuous measurement.
- Desktop
- Mobile
Stroke width
strokeWidth is the line's thickness in pixels. Raise it for a line that has to hold up in a dense dashboard; lower it to step a comparison series back behind a busier one.
X label
xLabel names what the x ticks are counting, for the cases the surrounding copy doesn't. Drawn dimmed where the ticks are muted — the reader needs it once, and the ticks continuously.
Y label
yLabel is the same, for the value axis.
- Desktop
- Mobile
Value format
valueFormat formats the y-axis ticks and the tooltip values together, so the two can never disagree.
Category format
categoryFormat rewrites the x tick labels without touching your data — how a key like 2025-03 becomes Mar while the rows keep their real values.
Grid
grid is the horizontal rules from the y ticks. Dropping them keeps the axes and their labels.
Zero baseline
zeroBaseline is off by default: a trend line reads best against its own range, and anchoring to zero flattens exactly the variation the chart exists to show. Turn it on when the reader is meant to compare magnitudes and the distance to zero is part of the answer.
The y axis is inferred from the data, not anchored to zero — for a trend line, a zero baseline usually flattens the exact variation the chart exists to show. zero-baseline turns it on when magnitudes rather than shape are the point.
Tooltip
tooltip is the crosshair and the panel that follows it, dropped together.
- Desktop
- Mobile
The tooltip is grouped on x — one row per series at the hovered position — because comparing series at the same moment is the question a multi-line chart is asked. Set tooltip="false" to drop both it and the crosshair.
Legend
legend is on by default from two series up. Force it off when the surrounding heading already names what's plotted.
Legend position
legendPosition moves the key above the plot with top — useful when the chart sits directly under a heading and the legend reads as part of it.
- Desktop
- Mobile
Legend align
legendAlign places the key horizontally. start lines it up with the plot's left edge, which is where the eye already is after reading the axis.
- Desktop
- Mobile
- Desktop
- Mobile
- Desktop
- Mobile
Legend orientation
legendOrientation flows the entries in a row or a column. vertical gives each series its own line — what long labels, or a value printed beside each label, want.
- Desktop · 8,680 visits
- Mobile · 7,630 visits
Guides
guides drops the axes, ticks, labels and grid *and* the margins they reserve, so the line fills the whole box. That plus a small height is the inline sparkline, for a stat tile or a table cell.
guides="false" drops the axes, ticks, labels and grid *and* the margins they reserve, so the marks fill the whole box. That plus a small height is the inline-sparkline shape — there's no separate component for it.
Height
height is the plot's height in pixels. Ignored once aspectRatio is set.
Aspect ratio
aspectRatio sizes the chart from its container's width instead of a fixed height — so it keeps its proportions across breakpoints. Resize the preview to watch it hold.
- Desktop
- Mobile
Loading
loading swaps in a skeleton of exactly the chart's height, so nothing around it moves when the data lands. An empty data array gets the empty state instead, also at full height.
- Desktop
- Mobile
- Desktop
- Mobile
@focus-change
Fires with the category under the pointer, and null when it leaves. The chart already shows a tooltip; this is for driving something *outside* it — a readout in a card header, a linked table row, a second chart scrubbing in step with this one.
Hovering:—
- Desktop
- Mobile
API Reference
Generated from the component's source — props, slots and emits as the component actually declares them.
Props
datarequiredRecord<string, unknown>[]seriesrequiredChartSeriesInput[]ariaLabelrequiredstringChartCurveName'linear'booleanfalsebooleanfalsebooleantruenumberEmits
focusChange[category: string | null]Types
interface ChartPlot {
x: number;
y: number;
width: number;
height: number;
/** Right edge, since marks need it more often than the width. */
right: number;
/** Bottom edge, which is also the x axis' baseline. */
bottom: number;
}type ChartSeriesInput = string | ChartSeries;type ChartCurveName = "linear" | "smooth" | "step" | "step-before" | "step-after";