Line Chart
Line and area chart for trends over categories.
The chart is a standalone component. Import the styles once for the whole family.
import { KuiLineChart } from '@kikita-labs/ui';
// Optional external legend:
import { KuiChartLegend } from '@kikita-labs/ui';Pass series aligned with categories. ariaLabel names what the chart shows; the chart type fallback is used when it is omitted.
<div class="chart-example">
<kui-line-chart ariaLabel="Sessions per day" [series]="series" [categories]="categories" />
</div>area fills the area under each line; it is a flag, not a separate chart type. patterns adds a hatch pattern to each area, and axes.xTitle and axes.yTitle name the axes.
<div class="chart-example">
<kui-line-chart
ariaLabel="Traffic by source"
[series]="series"
[categories]="categories"
[area]="true"
[patterns]="true"
[axes]="axes"
/>
</div>null, NaN and Infinity are gaps: the line breaks there and the value is never drawn as 0. tooltip and valueFormat replace the tooltip text and the number format.
<div class="chart-example">
<kui-line-chart
ariaLabel="Sessions with a missing day"
[series]="series"
[categories]="categories"
[tooltip]="formatTooltip"
[valueFormat]="formatValue"
size="sm"
/>
</div>Hide the inline legend and render it anywhere with kui-chart-legend and a template reference variable.
<div class="chart-example">
<kui-line-chart
#chartRef
ariaLabel="Sessions and sign-ups"
[series]="series"
[categories]="categories"
[legend]="false"
/>
<kui-chart-legend [chart]="chartRef" />
</div>loading shows a sparkline skeleton; a series input without a non-gap value shows the empty composition.
<div class="chart-example chart-example--stack">
<kui-line-chart
ariaLabel="Loading sessions"
[series]="series"
[categories]="categories"
[loading]="true"
size="sm"
/>
<kui-line-chart ariaLabel="Empty sessions" [series]="[]" size="sm" />
</div>Charts normalize their input before drawing, so gaps and invalid values never become zeros.
series[].datais aligned index for index withcategories; when the lengths differ the shorter side wins.null,NaNandInfinityare gaps: the mark is skipped and a line breaks there instead of connecting across. A gap is never drawn as0.- The value domain is
[min(0, min), max(0, max)]over all series, so the value axis always includes0and is never log-scaled or clamped. - The identity of a series is
id ?? name. Two series or slices sharing an identity get distinct keys (a,a#1, ...), and in development mode the chart logs one warning for a duplicate explicitidand for adataarray whose length differs fromcategories. - A flat domain (every value equal) is expanded so the axis never collapses, and ticks are rounded to the precision their step needs.
series[].colororslices[].coloris optional; without it the marks use a round-robin over the eight--kui-chart-series-1..8tokens in the order given.
The legend shows automatically when there is more than one series or slice; the legend input overrides it. Clicking an item hides that series or slice.
- Hiding a series does not recompute the value-axis domain, so the remaining series keep a stable scale across toggles.
- Hovering a legend item highlights the matching mark, and hovering a mark highlights its legend entry. Hidden items stay interactive; "every series hidden" is a legend-only state and not the empty composition.
- A hidden item is marked by more than its pressed state: the label is struck through and the swatch becomes an outline.
Standalone legend
Every chart implements KuiChartLegendSource: a public legendItems() and hoveredLegendId() read surface plus toggleLegendItem(id) and setHoveredLegendId(id). Pass the chart through a template reference variable to kui-chart-legend to render the legend anywhere, and set [legend]="false" on the chart so it is not drawn twice. Project a kuiChartLegendItem template (context KuiChartLegendItemContext: $implicit is the KuiChartLegendEntry, hovered the cross-highlight state) to replace the default button markup; wiring the toggle and hover calls is the template's job.
<kui-line-chart
#chartRef
ariaLabel="Sessions per day"
[series]="series"
[categories]="categories"
[legend]="false"
/>
<kui-chart-legend [chart]="chartRef" />size sets the plot height in CSS pixels; the chart follows the width of its container.
sizeissm,mdorlg(defaultmd): a plot height of 200, 280 or 360px.- The chart measures its container with a
ResizeObserverand draws at one user unit per CSS pixel, so text, strokes and marks keep their pixel size at any width. The axis padding fits the widest measured tick label and the axis title. - Server rendering and the first client render use a nominal width (320, 480 or 640px) inside a graphic whose height is already final, so hydration does not shift the layout.
- Tick labels are thinned by their measured width plus an 8px gap so they never overlap, and a label wider than its slot is cut with an ellipsis (the full text stays in the element
<title>). Axis text defaults to--kui-text-sm-size; the--kui-chart-axis-text-font-sizetoken changes it. - Axis titles come from
axes.xTitleandaxes.yTitleand are drawn in the SVG. There is no built-in zoom, pan or decimation: aggregate very dense data before passing it in.
loading shows a skeleton shaped like the chart instead of a generic spinner. Missing data renders an empty composition.
- The line skeleton is a wavy sparkline silhouette of shimmering dots built from
[kuiSkeleton]. - An empty or missing input (no non-gap value anywhere) always renders the empty composition: a dashed border, a faded chart icon and the "No data" text. It is not the
kui-empty-statecomponent. - The loading and empty texts are
chartmessages.
The tooltip text and the numbers on axes and in the legend can be replaced.
protected formatTooltip(point: KuiChartPoint): string {
return `${point.categoryLabel}: $${point.value.toLocaleString('en-US')}`;
}tooltipoverrides the default text:<series> · <category>: <value>.valueFormatdefaults to the compact notation of the locale (1.2Kand3.4Min English,1,5 Mio.in German). It formats axis ticks and the default tooltip and legend numbers, and is never used for the alt-table, which shows exact values.- The tooltip is dismissible (
Escapecloses it without moving focus or the pointer until a new trigger) and persistent (it stays until hover or focus ends). It follows the pointer and ignores pointer events, so it is not hoverable (WCAG 1.4.13); see Known gaps. - Accessible names, role descriptions, the loading and empty texts, the table headers and the point text are
chartmessages. Every chart takes amessagesinput for one-off overrides.
The Table button switches the same data to a table representation with exact values.
- The alt-table is the accessible-charts recommendation of pairing a graphic with a tabular alternative, not a decorative duplicate. It lists the visible series with exact (not compact-formatted) numbers and has a caption with the chart name.
- It lists every point of a dense chart whatever the chart draws.
- The Table button keeps keyboard focus when the view switches.
Colour is never the only way to tell series apart (WCAG 1.4.1).
- With more than one series, marks use a marker shape per series index (circle, square, diamond, up and down triangle, cross, plus, star) and the legend swatch shows the same shape. A single series stays circles, and hiding another series never changes the shape.
patterns(defaultfalse) fills each series with one of eight hatch patterns, a tinted ground with a hatch in the full series colour, and the legend swatch becomes a patterned square. On a line chart it applies toarea. Pattern ids are unique per chart instance, so many charts on one page never collide.- In forced colours (Windows High Contrast) the chart draws with the system colours
CanvasText,Canvas,GrayTextandHighlightonly. Series then differ by hatch pattern, by dash (lines) and by marker shape; patterns and dashes turn on automatically.
Tab enters a chart at the last focused mark and leaves with Tab; focus follows the arrows.
- ArrowLeft and ArrowRight move along the category axis in the same series, ArrowUp and ArrowDown between series at the same category, and Home and End jump to the first and last category of the series.
- Escape dismisses the tooltip; Enter and Space on a legend item toggle it.
- The roving position is a key (series and category), not an index, so hiding a series moves it to the nearest remaining mark and re-focuses that mark if focus was inside the chart. Focus mirrors hover: the focused mark highlights its series and opens the tooltip.
A chart draws every shape it is given, up to a limit for line marks.
- A line chart draws one DOM mark per point up to 500 marks (
KUI_CHART_MARK_LIMIT). Above that it draws only the mark that holds the tab stop; the keyboard model is index based and the table still lists every point. - Library measurements in Chromium (a two-series line chart and a one-series scatter chart of the same point count): 500 points paint in 46 ms with 1,629 DOM nodes, 2,000 points in 115 ms with 6,123 nodes and 5,000 points in 259 ms with 15,123 nodes. Hiding a series on the 2,000-point line chart paints in 27 ms. Decimation is out of scope.
Set defaults.lineChart once for the application or for a subtree. Each option resolves as local input > defaults.lineChart.<option> > built-in default.
import { provideKikitaUi } from '@kikita-labs/ui';
export const appConfig: ApplicationConfig = {
providers: [
provideKikitaUi({
defaults: {
lineChart: {
/* options below */
},
},
}),
],
};| Name | Type | Default | Description |
|---|---|---|---|
| size | 'sm' | 'md' | 'lg' | — | Chart size. |
| legend | boolean | — | Shows the chart legend. |
Set any of these on the component or an ancestor to restyle one part. Each token is optional: when it is not set, the part uses the role or scale token in the default column.
| Name | Type | Default | Description |
|---|---|---|---|
| --kui-chart-mark-stroke | CSS custom property | --kui-color-surface | Mark stroke |
| --kui-chart-pattern-ground-color | CSS custom property | --kui-color-surface | Ground under hatch patterns |
| --kui-chart-axis-title-color | CSS custom property | --kui-chart-axis-label-color | Axis title color |
| --kui-chart-empty-border | CSS custom property | --kui-color-border | Empty border color |
| --kui-chart-empty-color | CSS custom property | --kui-color-text-secondary | Empty color |
| --kui-chart-legend-item-bg-active | CSS custom property | --kui-color-surface-sunken | Legend item background, active |
| --kui-chart-legend-item-color-active | CSS custom property | --kui-color-text | Legend item color, active |
| Name | Type | Default | Description |
|---|---|---|---|
| --kui-chart-gap | CSS custom property | --kui-space-3 | Gap |
| --kui-chart-axis-text-font-size | CSS custom property | --kui-text-sm-size | Axis text font size |
| --kui-chart-loading-bars-gap | CSS custom property | --kui-space-3 | Loading bars gap |
| --kui-chart-loading-bars-padding | CSS custom property | --kui-space-4 | Loading bars padding |
| --kui-chart-empty-gap | CSS custom property | --kui-space-3 | Empty gap |
| --kui-chart-empty-radius | CSS custom property | --kui-radius-md | Empty corner radius |
| --kui-chart-empty-text-font-size | CSS custom property | --kui-text-sm-size | Empty text font size |
| --kui-chart-toolbar-gap | CSS custom property | --kui-space-3 | Toolbar gap |
| --kui-chart-legend-gap | CSS custom property | --kui-space-3 | Legend gap |
| --kui-chart-legend-item-gap | CSS custom property | --kui-space-2 | Legend item gap |
| --kui-chart-legend-item-radius | CSS custom property | --kui-radius-sm | Legend item corner radius |
| --kui-chart-legend-item-padding-inline | CSS custom property | --kui-space-1 | Legend item padding, inline |
| --kui-chart-legend-item-font-size | CSS custom property | --kui-text-sm-size | Legend item font size |
| --kui-chart-legend-swatch-size | CSS custom property | 12px | Legend swatch size |
Every text the component owns (accessible names, visible words, announcements) is a typed message with an English default. Override it for the application with provideKikitaUi({ messages }), or for a subtree with provideKuiMessages.
| Name | Type | Default | Description |
|---|---|---|---|
| loading | string | — | Accessible name of the loading state. Default Loading chart. |
| noData | string | — | Text shown when there is no data. Default No data. |
| viewChart | string | — | Button that shows the chart again from its data table. Default Chart. |
| viewTable | string | — | Button that shows the data table instead of the chart. Default Table. |
| columnCategory | string | — | Header of the category column in the data table. Default Category. |
| columnSeries | string | — | Header of the series column in the data table. Default Series. |
| columnLabel | string | — | Header of the label column in the data table. Default Label. |
| columnValue | string | — | Header of the value column in the data table. Default Value. |
| columnX | string | — | Header of the horizontal-axis column of a scatter chart's data table. Default X. |
| columnY | string | — | Header of the vertical-axis column of a scatter chart's data table. Default Y. |
| columnRadius | string | — | Header of the radius column of a bubble chart's data table. Default Radius. |
| legendLabel | string | — | Accessible name of the chart's inline legend. Default Legend. |
| lineLabel | string | — | Accessible name of a line chart. Default Line chart. |
| barLabel | string | — | Accessible name of a bar chart. Default Bar chart. |
| donutLabel | string | — | Accessible name of a donut chart. Default Donut chart. |
| scatterLabel | string | — | Accessible name of a scatter chart. Default Scatter chart. |
| lineRoleDescription | string | — | Role description of a line chart. Default line chart. |
| barRoleDescription | string | — | Role description of a bar chart. Default bar chart. |
| donutRoleDescription | string | — | Role description of a donut chart. Default donut chart. |
| scatterRoleDescription | string | — | Role description of a scatter chart. Default scatter chart. |
| pointWithCategory | KuiMessageFn<{ series: string; category: string; value: string; }> | — | Tooltip and name of a mark that has a category. Default {series} · {category}: {value}. |
| point | KuiMessageFn<{ series: string; value: string; }> | — | Tooltip and name of a mark without a category. Default {series}: {value}. |
| scatterPoint | KuiMessageFn<{ series: string; x: string; y: string; }> | — | Tooltip and name of a scatter point. Default {series}: ({x}, {y}). |
| bubblePoint | KuiMessageFn<{ series: string; x: string; y: string; radius: string; }> | — | Tooltip and name of a bubble. Default {series}: ({x}, {y}), radius {radius}. |
| slice | KuiMessageFn<{ label: string; value: string; percent: number; }> | — | Tooltip and name of a donut slice. Default {label}: {value} ({percent}%). |
Inputs verified against @kikita-labs/ui v2.0.0 public typings.
| Name | Type | Default | Description |
|---|---|---|---|
| series | readonly KuiChartCartesianSeries[] | - | Required. { id?, name, color?, data }, with data aligned index for index with categories. null, NaN and Infinity are gaps. Without a non-gap value the empty composition renders. |
| categories | readonly string[] | [] | Category labels aligned with each series data. The shorter side wins. |
| area | boolean | false | Fills the area under each line. It is not a separate chart type. |
| patterns | boolean | false | With area, fills each area with one of eight hatch patterns (colour independence). |
| axes | KuiChartAxesOptions | {} | { x?, y?, gridLines?, xTitle?, yTitle? }: show or hide an axis, choose the grid lines and set the axis titles drawn in the SVG. |
| size | 'sm' | 'md' | 'lg' | undefined | undefined | Plot height of 200, 280 or 360px (a square of up to that size for the donut). Falls back to the chart defaults key, then md. |
| loading | boolean | false | Shows a skeleton placeholder shaped like the chart instead of the chart. |
| legend | boolean | undefined | undefined | Shows the inline legend. Falls back to the chart defaults key, then true when there is more than one series or slice. |
| valueFormat | (value: number) => string | compact notation | Axis tick, default tooltip and legend number formatting. The default is the compact notation of the locale; the alt-table always shows exact values. |
| tooltip | (point: KuiChartPoint) => string | undefined | built-in text | Overrides the default tooltip and point label text. |
| ariaLabel | string | undefined | undefined | Accessible name of the chart as a whole. Falls back to the chart message for this type (Line chart); prefer a content-specific name. |
| messages | Partial<KuiChartMessages> | undefined | undefined | Per-instance text overrides: names, role descriptions, loading and empty text, table headers and point text. They win over scoped and root messages. |
| legendItems() / hoveredLegendId() | KuiChartLegendSource | - | Public read surface of the chart legend, used by kui-chart-legend. |
| toggleLegendItem(id) / setHoveredLegendId(id) | KuiChartLegendSource | - | Hide or show a series or slice, and set the cross-highlighted legend item. |
| kui-chart-legend [chart] | KuiChartLegendSource | - | Renders the legend of any chart through a template reference variable. Project a kuiChartLegendItem template (KuiChartLegendItemContext) to replace the buttons. |
| KuiBarChartOptions / KuiLineChartOptions / KuiScatterChartOptions / KuiDonutChartOptions | interfaces | - | Shape of defaults.barChart, lineChart, scatterChart and donutChart: size and legend. |
The chart is an SVG graphic with an accessible name, labelled marks and a table alternative.
- The
<svg>is the graphic:role="graphics-document"with the name and the role description (aria-roledescription). The wrapper has no role, and axis text, gridlines and decorative shapes arearia-hidden. - Each point is
role="graphics-symbol img"with its ownaria-label, the same text as the tooltip, as an indivisible graphical unit per the WAI-ARIA Graphics Module. ariaLabelhas a chart-type fallback name from thechartmessages and should describe what the chart shows. It is distinct from each mark's own label (its value).- The marks use a roving tabindex (see Keyboard). The inline legend is a named group of native
<button aria-pressed>items at least 24px high. - One shared tooltip overlay is retargeted between marks, so hover and keyboard focus show the same text. On pointer hover it appears at the pointer and follows it; keyboard focus anchors it to the focused mark. On touch, a tap pins the tooltip until an outside tap, Escape or a tap on a different mark, and it always renders below 768px.
- Pointer hit targets: the point marker plus a 3px margin.
- Text is at least 12px on screen; axis text defaults to 13px at any width.
prefers-reduced-motion: reducedisables the hover and dim transition and the donut sweep of sibling charts.- The alt-table is a real accessible alternative to the graphic.
The library checks keyboard, the accessibility tree, axe, forced-colours media emulation and reduced motion automatically. A session with a real screen reader has not been run and stays pending.
Documented limits of the installed package.
- The chart tooltip follows the pointer and is not hoverable, so it does not meet the hoverable part of WCAG 1.4.13 (it is dismissible and persistent). A tooltip that is hoverable and follows the cursor would catch the cursor, so anchoring it to the mark is an open design decision.
- There is no zoom, pan or decimation, and bar and scatter charts draw every shape (see Performance).
- The marker shapes and hatch patterns are engineering decisions that the designer still has to confirm. A formal assistive-technology pass with a real screen reader and a real Windows forced-colours session is still pending.