Line chart
Purpose
A Line chart shows how a value changes over a continuous interval — typically time. Use it when the reader needs to follow the direction and magnitude of change across a date range, a sprint cadence, or a build counter. Do NOT use a Line chart when the x axis is categorical (products, regions, teams) — use a Bar chart instead. Do NOT use a Line chart when only one data point exists — fall back to a KPI tile. Do NOT use a Line chart when more than seven series compete for attention — group the remainder into "Other" with a link to the underlying table, or switch to small multiples.
Anatomy
A Line chart is composed of:
- Title (required at md and lg): Headline label above the chart. Wraps to two lines maximum; truncates with ellipsis beyond that. Optional at sm density.
- Subtitle (optional): One line of context under the title. Does not wrap.
- Headline metric (optional): A KPI tile paired with the chart — a single summary number alongside the line. Number is the takeaway; the line shows the journey. Positioned in the top-right corner of the chart header.
- Action menu (optional): Per-widget controls — export, refresh, filter — pinned to the top-right corner.
- Y axis: Numeric values. Begins at zero by default; may start at the absolute floor when that value is meaningful and the dashboard builder has explicitly called it out.
- X axis: Time or continuous interval. Tick labels follow the Force UI date formatting rules and respond to the
xFormatoverride. - Gridlines: Horizontal only by default, anchored to y-tick positions. Vertical gridlines are available when comparing discrete time intervals, via the
gridlinesprop. - Line marks: One stroke per series. Stroke width is 2px at md and lg, 1.5px at sm and sparkline. Monotone cubic interpolation by default; straight linear segments available via the
curveprop. - Data markers: Small shapes placed at each data point. Shown automatically when fewer than 10 x values are visible. Each series uses a distinct shape so that series are differentiable by shape as well as color: circle (series 1), square (series 2), diamond (series 3), triangle-up (series 4), triangle-down (series 5), hexagon (series 6), pentagon (series 7).
- Crosshair: A 1px vertical line that tracks the pointer along the x axis, anchoring the tooltip content to the nearest x value.
- Tooltip: Appears on hover or keyboard focus. Shows the x label at the top, then one row per visible series — a color swatch, the series name, and the value formatted by
yFormat. Floats approximately 12px from the cursor, same as the Bar chart. - Legend: A row of color and shape swatches with series labels, always positioned below the chart. Required whenever more than one series is shown at md or lg density.
- Reference line (optional): A dashed horizontal threshold or average overlay. Stroke is 1px dashed
2px 2px, color--force-color-border-muted. Label aligned to the right edge of the plot area. - Area fill (optional): When the
areaprop is true, a gradient fill from the line down to the baseline at 15% opacity of the line color, fading to transparent. Enabled per-instance; not a variant. - Empty, loading, error, and no-permission overlays: Replace the plot area when data is unavailable. The title and header always remain visible regardless of state.
When to use
Use a Line chart when:
- The reader needs to follow a value across time, or compare a small set of values across the same continuous interval.
- The x axis is continuous: a date range, a sequence of timestamps, a build counter, a sprint index.
- At least two data points exist per series so a trend line can actually be drawn.
- No more than seven series are plotted at once.
- Direction and magnitude of change are the primary question, not current value alone.
Do NOT use a Line chart when:
- The x axis is categorical (products, regions, teams) — use a Bar chart. A line connecting categorical points implies a trajectory between them that does not exist.
- Only one data point exists or a single summary number is the takeaway — use a KPI tile.
- More than seven series compete for attention — group the remainder into "Other" with a "view all" link to the underlying table, or switch to small multiples.
- The widget is rendered below 240px wide — use the Sparkline variant or a KPI tile.
- The reader needs to compare parts of a whole at a given moment — use a Stacked area chart or a Donut chart.
Data requirements
| Requirement | Constraint | Behavior at the limit or when violated |
|---|---|---|
| Required fields | x (time or continuous), y (numeric) |
If either is missing, render the Empty state with an explanation. |
| Series identifier | Optional categorical field for multi-series. Inferred from data shape. | If not provided, a single line is drawn. |
| Accepted x types | ISO datetime, epoch ms, or numeric ordinal | Strings are not accepted. Pass categorical x to Bar chart instead. |
| Accepted y types | Number, including negatives. Booleans treated as 0 or 1. | Nulls are skipped. The line breaks across the gap with a reduced-opacity dashed segment. Nulls are never silently zeroed. |
| Minimum data points | 2 per series | Fewer than 2 falls back to a KPI tile component with the single value passed through. |
| Maximum data points | 1 000 per series (SVG mode), 10 000 (canvas mode) | Above the limit the chart downsamples and shows a banner explaining the reduced resolution. |
| Series count limit | 7 series | Above 7, remaining series are grouped into "Other" and a "view all" link points to the underlying data table. |
| Negative values | Allowed | Y axis extends below zero. A zero baseline is drawn. |
| Sort order | Chart sorts x ascending regardless of input order | The component cannot rely on the caller to pre-sort. |
| Aggregation | No client-side aggregation. Caller provides values at the desired interval. | If the interval is too dense for the rendered width, the chart downsamples and labels the new interval. |
Configuration
| Prop | Type | Default | Purpose |
|---|---|---|---|
| CONTENT | |||
title |
string |
required | Headline label above the chart. Wraps to two lines, then truncates with ellipsis. |
subtitle |
string |
undefined |
Optional one-line context under the title. Does not wrap. |
headlineMetric |
{ value, label, delta } |
undefined |
Optional KPI rendered in the chart header. Number is the takeaway; the line shows the journey. |
| DATA | |||
data |
Array<{x, y, series?}> |
required | The data payload. Schema defined in Data requirements above. |
groupBy |
string |
undefined |
Optional field name used to split a flat data array into series. |
referenceLines |
Array<{ value, label, style? }> |
[] |
Horizontal threshold or comparison lines overlaid on the plot area. |
xFormat |
string |
'auto' |
Custom date or numeric format for x-axis tick labels. Defaults to the Force UI date formatting rules. |
yFormat |
string |
'auto' |
Custom number format for y values and tooltip. Supports: 'currency', 'percent', 'abbreviated', 'raw'. |
| VISUAL | |||
variant |
'default' | 'reference' | 'forecast' | 'sparkline' |
'default' |
Approved variants documented in the Variants section. |
curve |
'monotone' | 'linear' |
'monotone' |
Line interpolation between data points. Use 'linear' when exact point-to-point accuracy matters more than visual smoothness. |
area |
boolean |
false |
When true, fills the area under each line with a 15% opacity gradient of the line color fading to transparent at the baseline. |
colorMapping |
Record<series, token> |
system | Overrides the automatic categorical color mapping for this chart instance. |
density |
'sm' | 'md' | 'lg' |
'md' |
Widget density. Affects type sizes, padding, tick density, and label rotation. |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Overrides the dashboard theme for this instance. |
showMarkers |
boolean | 'auto' |
'auto' |
When 'auto', markers appear only when fewer than 10 x values are visible. Pass true to always show, false to never show. |
| BEHAVIORAL | |||
legend |
'bottom' | 'none' |
'bottom' when multi-series; 'none' when single series |
Controls legend visibility. Always positioned below the chart when shown. |
tooltip |
boolean |
true |
When false, hover and focus produce no tooltip. |
crosshair |
boolean |
true |
When true, a 1px vertical line tracks the pointer along the x axis. |
gridlines |
'horizontal' | 'vertical' | 'both' | 'none' |
'horizontal' |
Controls which gridline axes are shown. Use 'vertical' only when comparing discrete time intervals. |
zoomable |
boolean |
false |
When true, scroll zooms the x axis, drag pans, and a reset affordance returns to the full range. |
crossFilter |
boolean |
false |
When true, selecting a region updates other widgets on the dashboard that share the same data binding. |
onMarkClick |
function |
undefined |
Optional drill-down callback. When set, markers become focusable and clicking fires the handler with the data row. |
exportable |
boolean |
true |
Whether the action menu exposes an Export option. |
interactive |
boolean |
true |
Master switch. When false, the chart renders as a static image — no hover, tooltip, or keyboard behavior. Use for print and email contexts. |
Variants
Five approved configurations. Use the variant prop to select one.
Default (single or multi-series)
The most common configuration. One or more lines drawn on shared axes using the categorical color sequence (--force-color-chart-1 through --force-color-chart-6). Multi-series automatically shows the legend. Use when the reader is following one or more values across a continuous interval.
Reference
The default variant with one or more reference lines overlaid on the plot area. Each reference line is a horizontal 1px dashed stroke in --force-color-border-muted with a label aligned to the right edge. Use when the reader is comparing a value against a fixed threshold or average target. Reference lines are supplied via the referenceLines prop. This variant name is a semantic signal in the dashboard definition; the lines themselves work on the default variant too.
Forecast
Divides each line at a transition point — "today" or a labeled start of projection — between observed data and projected values. Observed data renders as a solid 2px line. Projected data renders at 40% opacity with a 4px 4px dash. The transition point is marked with a short vertical dotted rule and a label (e.g., "Today" or "Forecast start"). Use for actual-vs-projected patterns where distinguishing past from future is the primary read.
Sparkline
A minimal line with no axes, no legend, no title, no tooltip, and no padding. Only the line shape is shown. When width drops below 240px and density is 'sm', the chart automatically falls into this form. Hover on a sparkline reveals a single value — the latest data point — within the parent element's context (e.g., the table cell or KPI tile). Use inside table cells, KPI tiles, or any constrained widget where only the trend shape is meaningful.
With headline metric
Not a separate variant value — use variant="default" and set headlineMetric. The KPI number is the primary takeaway; the line shows the path to that number. The metric sits in the chart header alongside the title.
States
The title and chart header always remain visible across all states. Only the plot area changes.
Default (data loaded): Lines are drawn across the full x range. Null gaps are rendered as a reduced-opacity (--force-opacity-subtle, 0.7) dashed 4px 4px segment connecting the surrounding solid segments — never zeroed.
Loading: Data is being fetched. Render 2–3 horizontal skeleton bars at varying widths inside the plot area to imply a chart shape. Use --force-color-bg-muted. Do not use a spinner for charts that take longer than 200ms to load.
Empty: No data for the selected period. Show a brief explanation and, where applicable, a call to action ("Try a wider time window" or "Connect a data source"). Use the EmptyState component inside the plot area.
Error: Data fetch failed. Show an explanation and a "Retry" button. Never silently fall back to the Empty state — the distinction between "no data" and "failed to load" must be visible to the user.
No permission: The viewer cannot access the underlying data. Replace the entire chart with a locked-state illustration and the message "You do not have access to this data." Do not expose any values or partial data.
Partial data: Some series or time intervals are missing. Render the available data, break the affected line at each gap, and add an inline banner below the chart explaining what period or series is missing (e.g., "Apr 24 to May 1, data unavailable for this period").
Single data point: Fewer than two points exist per series. Fall back to the KPI tile component and pass the single value through.
Interaction
| Behavior | Specification |
|---|---|
| Hover | The crosshair tracks the pointer along the x axis. The nearest data point on each series is highlighted with a filled marker at full opacity; all other series dim to --force-opacity-subtle (0.7). |
| Tooltip | Floats approximately 12px from the cursor. Content is anchored to the nearest x value: x label at top, then one row per visible series — a 10px color swatch circle, the series name, and the formatted value. Opens on hover or keyboard focus; closes on pointer leave or Escape. |
| Touch | Long-press on a data point opens the tooltip. Tap elsewhere closes it. No hover required. |
| Marker click | No-op by default. When onMarkClick is set, markers become focusable and clicking fires the handler with the corresponding data row. |
| Legend item click | Toggles that series' visibility. Other series remain unchanged. Toggled state persists in URL or local state per the dashboard convention. |
| Zoom and pan | Off by default. When zoomable is true, scroll zooms the x axis, drag pans, and a "Reset zoom" affordance returns to the full range. |
| Cross-filter | Off by default. When crossFilter is true, selecting a region updates other widgets on the dashboard sharing the same data binding. |
| Keyboard navigation | Tab focuses the chart container. Left and Right arrows step through x values, opening the tooltip at each position. Enter fires onMarkClick if set. Escape closes the tooltip and returns focus to the chart container. Continued tabbing moves focus through legend items (left to right), then the action menu, then out of the chart. |
Accessibility
| Requirement | Specification |
|---|---|
| Color contrast | Every foreground and background pair meets WCAG 2.2 AA. Tokens in this spec resolve to compliant pairs by default. Custom colorMapping values must not break contrast; violating values are rejected at design review. |
| Color-blind safety | The categorical color palette passes Deuteranopia, Protanopia, Tritanopia, and Achromatopsia simulation. Series are differentiated by color AND marker shape AND legend label. Color alone is never the only signal. |
| Screen reader | The chart element has an accessible name (aria-label) derived from title. A summary sentence is exposed to assistive technology (e.g., "Build duration, last 30 days. Backend averaged 12.4 min"). A data table fallback is reachable via a "View as table" affordance that appears on keyboard focus. |
| Keyboard reachability | Every interaction is reachable via keyboard. Tab order: chart container → legend items (left to right) → action menu. |
| Focus indication | A visible focus ring (--force-shadow-focus-ring + --force-color-border-focus) appears on the chart container, on each legend item, and on each focusable data marker. Focus state is visually distinct from hover. |
| Reduced motion | When prefers-reduced-motion: reduce is set, all draw-in animations and data-update transitions are removed. Tooltip and focus states appear immediately without animation. |
| Text scaling | The component reflows at up to 200% text size without clipping or overlap. Axis labels collapse first, then tick density reduces, then the legend wraps to additional rows. |
Sizing and responsive behavior
| Density | Width range | Use case | What changes |
|---|---|---|---|
| Sparkline | < 240px | Table cell, KPI tile, compact card | No axes, no legend, no tooltip. Hover shows the latest value in the parent context. |
Small (sm) |
240–359px | Narrow column, inline widget | Axes shown without titles. Ticks reduced to two per axis. Legend hides; series are identified by marker shape and a label in the title area. |
Medium (md) |
360–599px | Standard dashboard widget | Compact axis labels. Numbers abbreviated. Legend wraps below. Tooltip shows one row per series. |
Large (lg) |
≥ 600px | Full-width report | Full axis labels. Multiple gridlines. Legend below. Tooltip shows extended metadata. |
Density does not change the variant. A 'forecast' chart at 'sm' density still shows the solid/dashed distinction; it has fewer labels and ticks.
Guidance
Choose the variant based on the primary question:
- Default: "How has this value changed over time?" — one or more series on shared axes.
- Reference: "Is the value above or below a target?" — add a
referenceLinesentry alongsidevariant="default". - Forecast: "Where is the value heading?" — observed data solid, projected data dashed at 40% opacity.
- Sparkline: "What is the shape of this trend?" — no labels, minimum chrome, maximum information density.
Choose the curve based on the data type:
- Monotone (default): Temporal data — error rates, build times, user counts. Smooth curves communicate continuous change.
- Linear: Step-like or financial data — version numbers, prices at exact timestamps. Straight segments prevent implied values between points.
Common mistakes to avoid:
- Do NOT truncate the y axis to amplify a small change. Start at zero unless the absolute floor is explicitly meaningful — a chart with a truncated y axis misrepresents magnitude.
- Do NOT plot more than seven series on one chart. The reader cannot separate more than that; group the remainder into "Other."
- Do NOT use a line chart when the x axis is categorical. Connecting categorical points with a line implies a trajectory that does not exist.
- Do NOT use
area: trueon multi-series charts where overlapping fills obscure the lines — consider a Stacked area chart instead. - Do NOT set
interactive: falsefor charts that will be embedded in interactive dashboards — this removes all hover, focus, and keyboard behavior.
Token usage
Line marks
- Stroke color:
--force-color-chart-1through--force-color-chart-6(categorical sequence) - Stroke width: 2px at
mdandlg; 1.5px atsmand sparkline - Forecast projected segment: 40% opacity, dash
4px 4px - Null gap segment:
--force-opacity-subtle(0.7), dash4px 4px
Area fill (when area: true)
- Fill:
color-mix(in srgb, var(--force-color-chart-N) 15%, transparent) - Direction: fades from 15% opacity at the line to transparent at the baseline
Background and structure
- Chart background:
--force-color-bg-surface - Gridlines:
--force-color-border-muted, 1px solid - Zero baseline (when y range includes 0 or negatives):
--force-color-border-default, 1px solid - Crosshair:
--force-color-border-muted, 1px solid
Reference line
- Stroke:
--force-color-border-muted, 1px, dash2px 2px - Label:
--force-color-text-tertiary,--force-font-size-xs, right-aligned to plot edge
Axis labels
- Tick label color:
--force-color-text-tertiary - Tick label size:
--force-font-size-xs(12px) - Axis title color:
--force-color-text-secondary - Axis title size:
--force-font-size-xs
Tooltip
- Background:
--force-color-bg-inverse - Text:
--force-color-text-on-inverse - Series swatch: 10px diameter circle using the series' chart color token
- Cursor offset: ~12px
Interaction and states
- Dimmed (unfocused series):
--force-opacity-subtle(0.7) - Focus ring:
--force-shadow-focus-ring+--force-color-border-focus - Loading skeleton:
--force-color-bg-muted
Motion
- Draw-in on mount:
--force-duration-normal(250ms),--force-easing-standard - Data update transition:
--force-duration-normal(250ms),--force-easing-standard - Tooltip open:
--force-duration-fast(150ms),--force-easing-standard - Reduced motion override: all durations collapse to 0ms when
prefers-reduced-motion: reduce
Related components
| Component | Relationship | When to redirect |
|---|---|---|
| Area chart | Same shape, filled region. | When the reader cares about cumulative volume under the line, or when multiple series sum to a meaningful total. |
| Bar chart | Categorical sibling. | When the x axis is discrete categories — products, regions, teams — not time. |
| Sparkline | Inline variant of this chart. | When the chart fits inside a table cell or KPI tile and only the trend shape is meaningful. Use via variant="sparkline" rather than a separate component. |
| KPI tile | Single-value fallback. | When only one data point exists, or when a single summary number is the primary takeaway. |
| Stacked area | Parts of a whole over time. | When multiple series sum to a total and the composition of that total matters to the reader. |
| Scatter plot | Different intent. | When the relationship is x vs y and x is continuous and unordered — not a time series. |