Menu


Line chart


A trend over time: one line, or the same with a soft area under it, or this period against the last as a dashed line, with a value axis, light grid lines and one marked point.

Customize the preview


Choices
Show
View
  • Cost
Spend per day, Oct 4 to today
PeriodCost
Oct 4$62
Oct 5$118
Oct 6$112
Oct 7$190
Oct 8$236
Oct 9$280
Today$345

Options


Line chart options
ChoiceValuesDefaultWhat it does
typesmbk-line-chart--type-<value>linearealine
The line alone, or with a soft fill of its color under it. A second series, the period it is compared with, is drawn as a dashed line either way.

Partssmbk-line-chart__legendsmbk-line-chart__bodysmbk-line-chart__y-axissmbk-line-chart__y-labelsmbk-line-chart__plotsmbk-line-chart__gridsmbk-line-chart__gridlinesmbk-line-chart__baselinesmbk-line-chart__seriessmbk-line-chart__svgsmbk-line-chart__areasmbk-line-chart__linesmbk-line-chart__comparisonsmbk-line-chart__drop-linesmbk-line-chart__markersmbk-line-chart__pointsmbk-line-chart__readoutsmbk-line-chart__x-axissmbk-line-chart__x-labelsmbk-line-chart__tablesmbk-line-chart__data

Examples


Usage

A week of spend with one value marked: the dot, a line down to the baseline and its day and amount above it. The value axis runs from $0 to a round number over the highest value.

  • Cost
Spend per day, Oct 4 to today
PeriodCost
Oct 4$62
Oct 5$118
Oct 6$112
Oct 7$190
Oct 8$236
Oct 9$280
Today$345

With an area

The same line with a soft fill of its color under it, for an amount that adds up over time.

  • Cost
Spend per day, with an area
PeriodCost
Oct 4$62
Oct 5$118
Oct 6$112
Oct 7$190
Oct 8$236
Oct 9$280
Today$345

Against last week

This week against the week before. The week before is gray and dashed, so the two lines differ by more than color, and the legend names both in the same order.

  • This week
  • Last week
Spend per day, this week and last
PeriodThis weekLast week
Mon$80$118
Tue$240$150
Wed$95$132
Thu$328$205
Fri$130$158
Sat$265$196
Today$175$172

A month

Thirty days, labelled once a week and today. With many values, mark none and let the summary say what matters.

  • Visits
Visits per day, last 30 days
PeriodVisits
Sep 11420
Sep 12455
Sep 13485
Sep 14506
Sep 15514
Sep 16421
Sep 17408
Sep 18481
Sep 19464
Sep 20452
Sep 21451
Sep 22461
Sep 23394
Sep 24425
Sep 25551
Sep 26586
Sep 27614
Sep 28633
Sep 29639
Sep 30544
Oct 1530
Oct 2602
Oct 3585
Oct 4576
Oct 5576
Oct 6589
Oct 7523
Oct 8556
Oct 9682
Today716

With its data shown

Every value in a table, in a closed disclosure under the chart. Without it, the table is still there for screen readers.

  • Cost
  • Last week
Data
Spend per day, with its data
PeriodCostLast week
Oct 4$62$84
Oct 5$118$102
Oct 6$112$126
Oct 7$190$140
Oct 8$236$151
Oct 9$280$168
Today$345$190

In a narrow space

A narrow layout shows every second label under the plot, keeping today.

  • Cost
Spend per day, narrow
PeriodCost
Oct 4$62
Oct 5$118
Oct 6$112
Oct 7$190
Oct 8$236
Oct 9$280
Today$345

Guidelines


Purpose

Shows how one amount moved over time, such as spend per day or visits per week, so a reader sees its direction and its peaks at a glance. It can set this period against the last one, or fill the area under the line for an amount that adds up.

When to use

  • A value over days, weeks or months, where the shape of the change matters more than each number.
  • This period against the last one, such as this week against last week.

When not to use

  • To compare separate amounts, such as days or products side by side. Use a bar chart.
  • For parts of a whole. Use a donut chart, or a bar chart stacked by kind.
  • When one number answers the question. Show the number, with a sparkline beside it if its shape helps.
  • For more than two lines. Split them into small charts side by side that share one scale.

Usage rules

Do

  • Say in the label what is counted and over what time, and in the summary what it shows.
  • Start the value axis at zero, as it does unless told otherwise.
  • Mark the one value the reader should look at, such as today or the highest.

Don't

  • Draw more than two lines in one chart.
  • Leave out the legend when there are two lines.
  • Put a value only in the picture: the table holds them all.
  • This week
  • Last week
Spend per day, do
PeriodThis weekLast week
Mon$80$118
Tue$240$150
Wed$95$132
Thu$328$205
Fri$130$158
Sat$265$196
Today$175$172
Do

Compare a period with the one before as a second, dashed line, named in the legend.

Spend per day, don't
PeriodThis weekLast week
Mon$80$118
Tue$240$150
Wed$95$132
Thu$328$205
Fri$130$158
Sat$265$196
Today$175$172
Don't

Without a legend the reader cannot tell which line is which.

Anatomy

  • The block: the legend, the body and the table, one under the other.
  • The legend part: a list of legend items, the series swatch for the line and the muted swatch for the period it is compared with.
  • The body part, the picture: the y-axis part with a y-label part for each round value, highest first; the plot part beside it; the x-axis part under the plot with an x-label part for each label shown.
  • In the plot, the grid part: a gridline part at each round value and the baseline part at zero; the series part, holding everything drawn from the values: the svg part with the area part (the area type), the comparison part (a second series, dashed) and the line part; and, for a marked value, the drop-line part down to the baseline and the marker part holding the point part and the readout part with its label and value. Markup written by hand may leave out the series part.
  • The table part: every value in a table, for screen readers; or the data part, the same table in a closed disclosure under the chart.

Behavior

The chart fills the width it is given; the plot keeps one height. The value axis runs from zero, or from the lowest value when one is below zero, to a round number above the highest value, in about four round steps; give two charts the same lowest and highest values and their lines compare. Each value sits above its label, the labels spread evenly across the plot, the first and the last kept inside it. With many values, show every seventh label or so: the last label always shows. A marked value gets a dot, a line down to the baseline and its label and value above it. In a narrow layout every second label under the plot is left out, keeping the last. The first time the chart comes into view the lines, the area and the marked value are uncovered together from left to right, while the axes and grid lines stay, and later changes to the data land at once. Under reduced motion, and in markup written by hand, it appears at once. Nothing takes focus. The wrapper writes the values in English unless locale names another language or format writes them, so a page prepared on a server shows the same numbers once its script runs.

Accessibility


What it does for you

  • The picture is an image named by the summary. It takes no focus.
  • Every value is in a table titled by the chart's label, a row for each label and a column for each series. It is out of sight, for screen readers, unless the page shows it in the disclosure under the chart, which the keyboard reaches.
  • The two lines differ by more than color: the period compared with is gray and dashed, and the legend names both in the same order.
  • The marked value is drawn, not hovered: it needs no pointer, and its value is in the table too.
  • In high contrast mode the plot keeps its colors, so the lines still match the legend.

What you must do

  • Write the summary as the chart's main message, in a sentence: what rose or fell, from what to what.
  • In markup written by hand, name the body with role="img" and its summary, and keep the table.

How to test

  • With a screen reader, the chart is read as an image with its summary, and the table is reachable with its caption, a row per label and a column per series.
  • With the data shown, Tab reaches the disclosure and Enter or Space opens it to the table.
  • Check that every value in the picture is also in the table.
  • With text at 200% and the page 320 pixels wide, the chart still fits its width and its labels stay readable.
  • In a forced-color mode, the lines keep their colors and still match the legend.

Keyboard

Line chart keys
KeyWhat it does
Tab
When the page shows the values, moves focus to the disclosure under the chart. The chart itself takes no focus.
EnterorSpace
On the disclosure, opens or closes it.

Source


The files the line chart is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
line-chart.css
/* smbk-line-chart — Line chart. Figma "Line chart" 7580:757.
 * The legend over the chart; then the value labels at the left and the plot beside them, light grid lines at the
 * round values and the baseline at zero; the labels under the plot. The line is drawn in the series color from its
 * values, 2 wide, with a soft area of the same color under it for the area type; a second series, the period it
 * is compared with, is a dashed gray line under it. One value may be marked: a dot ringed in the page's color, a
 * line down to the baseline and a dark label above with its label and value. The marks are SVG stretched to the
 * plot, the line keeping its width; the words are text, so they keep their size. A narrow layout drops every
 * second label under the plot. The first time the chart comes into view its series is uncovered from left to
 * right; nothing else moves and nothing takes focus. */

.smbk-line-chart {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  min-width: 0;
}

/* The block frames the words it hides for screen readers, so they stay inside it wherever it is placed: without a
   frame they hang from the page itself and stretch it, past the end of an area that scrolls. No weight, so a parent
   that places the block keeps its say. */
:where(.smbk-line-chart) {
  position: relative;
}

.smbk-line-chart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--smbk-space-stack-xs) var(--smbk-space-inline-xl);
  margin: 0;
  padding: 0;
}

/* The value labels, the plot and, under the plot, its labels. */
.smbk-line-chart__body {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--smbk-space-stack-md) var(--smbk-space-inline-md);
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
  text-transform: var(--smbk-typography-body-xs-text-transform);
}

/* The value labels, highest first, spread from the top of the plot to its foot, so each is centred on its line. */
.smbk-line-chart__y-axis {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  height: var(--smbk-size-chart-plot-height);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.smbk-line-chart__plot {
  position: relative;
  height: var(--smbk-size-chart-plot-height);
}

/* The scale runs between the middles of the top and bottom value labels: half a line of their text in. */
.smbk-line-chart__grid {
  position: absolute;
  inset: calc(var(--smbk-typography-body-xs-font-size) * var(--smbk-typography-body-xs-line-height) / 2) 0;
}

.smbk-line-chart__gridline,
.smbk-line-chart__baseline {
  position: absolute;
  right: 0;
  left: 0;
  height: var(--smbk-border-width-hairline);
  background: var(--smbk-color-border-subtle);
  translate: 0 -50%;
}

.smbk-line-chart__baseline {
  background: var(--smbk-color-data-reference-line);
}

/* Everything drawn from the values, over the grid: the lines and the area, and the marked value. The wrapper draws
   it so the entrance uncovers them together; markup written by hand may leave it out. */
.smbk-line-chart__series {
  position: absolute;
  inset: 0;
}

.smbk-line-chart__svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.smbk-line-chart__line,
.smbk-line-chart__comparison {
  fill: none;
  stroke: var(--smbk-color-data-series);
  stroke-width: var(--smbk-border-width-emphasis);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* The period compared with: gray and dashed, so it is told apart by more than its color. */
.smbk-line-chart__comparison {
  stroke: var(--smbk-color-data-reference-line);
  stroke-dasharray: var(--smbk-size-4) var(--smbk-size-4);
  stroke-linecap: butt;
}

.smbk-line-chart__area {
  fill: var(--smbk-color-data-series);
  fill-opacity: var(--smbk-opacity-ghost);
  stroke: none;
}

/* ---- the marked value: a line down to the baseline, the dot, and its label and value above ---- */

.smbk-line-chart__drop-line {
  position: absolute;
  width: var(--smbk-border-width-hairline);
  background: var(--smbk-color-data-reference-line);
  translate: -50% 0;
}

.smbk-line-chart__marker {
  position: absolute;
  width: 0;
  height: 0;
}

/* The dot is ringed in the page's color, so it stands clear of the line it sits on. */
.smbk-line-chart__point {
  position: absolute;
  width: var(--smbk-size-8);
  height: var(--smbk-size-8);
  border: var(--smbk-border-width-emphasis) solid var(--smbk-color-surface-default);
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-data-series);
  translate: -50% -50%;
}

.smbk-line-chart__readout {
  position: absolute;
  bottom: var(--smbk-size-8);
  padding: var(--smbk-space-2) var(--smbk-space-6);
  border-radius: var(--smbk-radius-control-sm);
  background: var(--smbk-color-surface-inverse);
  color: var(--smbk-color-text-inverse);
  font-family: var(--smbk-typography-label-sm-font-family);
  font-size: var(--smbk-typography-label-sm-font-size);
  font-weight: var(--smbk-typography-label-sm-font-weight);
  line-height: var(--smbk-typography-label-sm-line-height);
  letter-spacing: var(--smbk-typography-label-sm-letter-spacing);
  text-transform: var(--smbk-typography-label-sm-text-transform);
  white-space: nowrap;
  translate: -50% 0;
}

/* Near an end of the plot the label starts or ends at the dot, so it stays over the plot. */
.smbk-line-chart__readout--start {
  translate: calc(-1 * var(--smbk-size-8)) 0;
}

.smbk-line-chart__readout--end {
  translate: calc(-100% + var(--smbk-size-8)) 0;
}

/* ---- the labels under the plot, each centred under its value, the first and last kept inside the plot ---- */

.smbk-line-chart__x-axis {
  grid-column: 2;
  position: relative;
  height: calc(var(--smbk-typography-body-xs-font-size) * var(--smbk-typography-body-xs-line-height));
}

.smbk-line-chart__x-label {
  position: absolute;
  top: 0;
  white-space: nowrap;
  translate: -50% 0;
}

.smbk-line-chart__x-label:first-child {
  translate: 0 0;
}

.smbk-line-chart__x-label:last-child {
  translate: -100% 0;
}

/* The table, for screen readers only, unless the page shows it in the disclosure under the chart. */
/* .smbk-line-chart__table: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

/* A table takes the room its rows need whatever width and height it is given, so the hidden one is full size, only
   unseen. It hangs from the block's end corner, reaching back and up, the way a page never scrolls: it adds no room
   to the block or to any area that scrolls around it, and stays a table for screen readers. */
.smbk-line-chart__table {
  inset-block-end: 0;
  inset-inline-end: 0;
}

/* A narrow space shows every second label under the plot, keeping the last. */
@container style(--smbk-layout: narrow) {
  .smbk-line-chart__x-label--minor {
    display: none;
  }
}

/* ---- motion: the entrance ----
   The wrapper marks the chart `data-entrance` the first time it comes into view (simbolik/components/entrance.ts):
   waiting while it is out of view, the series hidden; playing once it is seen, the series (lines, area and marked
   value together) uncovered from the start of the plot to its end with the chart entrance purpose, as one edge
   moving across, while the grid and the axes stay. The edges run a little past the plot on each side, and well
   over and under it, so the line's round ends, the dot and its label are never cut once uncovered. Without the
   mark (markup written by hand, the rendered pictures, after the entrance) the series is simply drawn, and under
   reduced motion nothing moves. Only the clip changes, so nothing shifts. */

@media screen and (prefers-reduced-motion: no-preference) {
  .smbk-line-chart[data-entrance="waiting"] .smbk-line-chart__series {
    clip-path: inset(-100% calc(100% + var(--smbk-size-8)) -100% calc(-1 * var(--smbk-size-8)));
  }

  .smbk-line-chart:is([data-entrance="load"], [data-entrance="playing"]) .smbk-line-chart__series {
    animation: smbk-line-chart-reveal var(--smbk-motion-chart-entrance-reveal-duration) var(--smbk-motion-chart-entrance-easing) backwards;
  }
}

@keyframes smbk-line-chart-reveal {
  from {
    clip-path: inset(-100% calc(100% + var(--smbk-size-8)) -100% calc(-1 * var(--smbk-size-8)));
  }

  to {
    clip-path: inset(-100% calc(-1 * var(--smbk-size-8)) -100% calc(-1 * var(--smbk-size-8)));
  }
}

/* ---- high contrast (forced colors) ---- */
/* The plot keeps its colors, so the line still matches its legend swatch and the dashed line stays gray. */

@media (forced-colors: active) {
  .smbk-line-chart__plot {
    forced-color-adjust: none;
  }
}
Tokens it reads
Line chart tokens
Token and CSSValuePreviewFoundation
space.stack.md--smbk-space-stack-mdcomfortable: 12pxcompact: 8pxchanges with densitySpacing
space.stack.xs--smbk-space-stack-xs4pxSpacing
space.inline.xl--smbk-space-inline-xlcomfortable: 16pxcompact: 12pxchanges with densitySpacing
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
size.chart.plot-height--smbk-size-chart-plot-height196pxSize
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.subtle--smbk-color-border-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
color.data.reference-line--smbk-color-data-reference-linelight: default: #373d43 · cockpit: #4e555edark: default: #e7ebf0 · cockpit: #9ba5b1changes with themeColor
color.data.series--smbk-color-data-serieslight: default: #3b5ce3 · cockpit: #0a85a1dark: default: #7fa0ff · cockpit: #34a2bfchanges with themeColor
border-width.emphasis--smbk-border-width-emphasis2pxBorder width
size.4--smbk-size-44pxSize
opacity.ghost--smbk-opacity-ghost0.1Opacity
size.8--smbk-size-88pxSize
color.surface.default--smbk-color-surface-defaultlight: #f3f5f8dark: #181a1cchanges with themeColor
radius.full--smbk-radius-full9999pxRadius
space.2--smbk-space-22pxSpacing
space.6--smbk-space-66pxSpacing
radius.control.sm--smbk-radius-control-sm4pxRadius
color.surface.inverse--smbk-color-surface-inverselight: #2d3237dark: #e7ebf0changes with themeColor
color.text.inverse--smbk-color-text-inverselight: #f9fafbdark: #232527changes with themeColor
typography.label-sm--smbk-typography-label-sm-font-family--smbk-typography-label-sm-font-size--smbk-typography-label-sm-font-weight--smbk-typography-label-sm-line-height--smbk-typography-label-sm-letter-spacing--smbk-typography-label-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
motion.chart-entrance.reveal-duration--smbk-motion-chart-entrance-reveal-duration1000msMotion
motion.chart-entrance.easing--smbk-motion-chart-entrance-easingcubic-bezier(0, 0.7, 0.5, 1)Motion
Manifest
line-chart.manifest.json
{
  "name": "line-chart",
  "block": "smbk-line-chart",
  "summary": "A trend over time: one line, or the same with a soft area under it, or this period against the last as a dashed line, with a value axis, light grid lines and one marked point.",
  "adapt": "css",
  "axes": {
    "type": {
      "values": [
        "line",
        "area"
      ],
      "default": "line",
      "description": "The line alone, or with a soft fill of its color under it. A second series, the period it is compared with, is drawn as a dashed line either way."
    }
  },
  "flags": {},
  "parts": [
    "legend",
    "body",
    "y-axis",
    "y-label",
    "plot",
    "grid",
    "gridline",
    "baseline",
    "series",
    "svg",
    "area",
    "line",
    "comparison",
    "drop-line",
    "marker",
    "point",
    "readout",
    "x-axis",
    "x-label",
    "table",
    "data"
  ],
  "figmaNodeId": "7580:757"
}