Bar charts

This type of graph is used to represent one-dimensional data (in our example, a timeline). It's based on CSS grids and custom properties, a technique inspired by an article by Miriam Suzanne on CSS Tricks with a slight enhancement to improve accessibility. Here's how to use it:

  1. On the table itself, the --scale custom property is used to define the maximum value for the graph, in order to determine its scale. Concretely, a grid will be generated with:
    • the first column dedicated to header cells <th> arbitrarily set to 12.5em, though being configurable through --offset;
    • then the CSS repeat() function creates a column per scale unit — in the example, 3000 columns;
    • and finally the last column measuring 10ch, literally meaning enough space for ten "0" characters have a look to CSS units rudiments, documented in Every Layout by Andy Bell and Heydon Pickering.
  2. On each cell <td>, a --value custom property allows to place it on the grid, applied to grid-column-end. Moreover, thanks to clever calculations based on this value, the background gradient is sized and positioned to reflect the proportion represented by this value on the given scale (from green for almost nothing to red for almost everything).
  3. In each cell, the content must include the value and its unit in a <span> element, possibly tagged with <abbr> (and aria-label to complement title) if a title can explicit the unit. This value is pushed to the right of the grid, and its text serves as a mask for the background gradient — thanks to S. Shaw's trick to apply background-clip: text as a progressive enhancement — allowing it to be the corresponding color at the end of the gradient for the given position.

Switch
Allows you to disable styles on the following table.

Loading time for ffoodd.fr
Cumulative loading time
Time: backend ms
Time: Frontend 96 ms
Delay: first byte 102 ms
Delay: last byte 129 ms
Delay: first image 188 ms
Delay: first CSS 194 ms
Delay: first JS 326 ms
DOM Interactive 836 ms
DOM loading 836 ms
DOM complete 2561 ms
HTTP traffic completed 2980 ms
HTML
<table class="chaarts bar" style="--scale: 3000">
	<caption id="caption-1">[…]</caption>
	<thead class="sr-only">
		<tr>
			<td></td>
			<th scope="col">[…]</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th scope="row">[…]</th>
			<td style="--value: 4">
				<span>[…]</span>
			</td>
		</tr>
		<tr>[…]</tr>
	</tbody>
</table>
css
.chaarts.bar {
  --offset: 12.5em;
}

.chaarts.bar caption {
  text-align: initial;
  text-indent: calc(var(--offset) + 1rem);
}

.chaarts.bar tr {
  display: grid;
  grid-auto-rows: 1fr;
  grid-row-gap: 0.5rem;
  grid-template-columns: minmax(min-content, var(--offset)) repeat(var(--scale, 100), minmax(0, 1fr)) 10ch;
  transition: opacity 0.2s var(--move);
}

.chaarts.bar tr:nth-of-type(1n + 1) {
  --background: var(--checkers);
}

.chaarts.bar tr:nth-of-type(2n + 2) {
  --background: var(--hexagons);
}

.chaarts.bar tr:nth-of-type(3n + 3) {
  --background: var(--triangles);
}

.chaarts.bar tr:nth-of-type(4n + 4) {
  --background: var(--zig);
}

.chaarts.bar tr:nth-of-type(5n + 5) {
  --background: var(--stripes);
}

.chaarts.bar tr:nth-of-type(6n + 6) {
  --background: var(--dots);
}

.chaarts.bar th {
  grid-column: 1/1;
  margin: 0.5rem 0 0;
  padding: 0 1rem 0 0;
  text-align: end;
}

.chaarts.bar td {
  --size: calc(var(--scale, 100) * 100%);
  --position: calc(var(--value, 0) / var(--scale, 100) * 100%);
  background: linear-gradient(to right, var(--chaarts-green), var(--chaarts-gray), var(--chaarts-blue), var(--chaarts-purple), var(--chaarts-red)) var(--position) 0% / var(--size) 100%, var(--background) center/contain;
  background-blend-mode: hard-light;
  grid-column: 2 / max(2, var(--value, 0));
  margin: 0.5rem 0 0;
  position: relative;
}

.chaarts.bar span {
  background: inherit;
  background-clip: text;
  color: transparent;
  font-weight: bold;
  inset-inline-start: 100%;
  line-height: 1.5;
  padding-inline-start: 0.5ch;
  position: absolute;
}

.chaarts.bar:hover tr {
  opacity: 0.5;
}

.chaarts.bar:hover tr:hover {
  opacity: 1;
}

@media (prefers-contrast: more) {
  .chaarts.bar td {
    background: var(--chaarts-blue);
  }
  .chaarts.bar span {
    background: var(--background-lighter);
    background-clip: unset;
    color: var(--chaarts-blue);
  }
}

Waterfall

The principle is the same for this variant, except for one detail: we also manage the starting point for each measurement — which is, very simply, the value of the previous point… However, all the values must be passed as variables on the parent <table>.

Switch
Allows you to disable styles on the following table.

Loading time for ffoodd.fr
Cumulative loading time
Time: backend ms
Time: Frontend 96 ms
Delay: first byte 102 ms
Delay: last byte 129 ms
Delay: first image 188 ms
Delay: first CSS 194 ms
Delay: first JS 326 ms
DOM interactive 836 ms
DOM loading 836 ms
DOM complete 2561 ms
HTTP traffic completed 2980 ms
HTML
<table class="chaarts bar waterfall"
 style="--scale: 3000; --1: 4; --2: 96; --3: 102; --4: 129; --5: 188; --6: 194; --7: 326; --8: 836; --9: 836; --10: 2561; --11: 2980;">
</table>
css
.chaarts.bar.waterfall tr:nth-of-type(1) td {
  grid-column: var(--0, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(2) td {
  grid-column: var(--1, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(3) td {
  grid-column: var(--2, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(4) td {
  grid-column: var(--3, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(5) td {
  grid-column: var(--4, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(6) td {
  grid-column: var(--5, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(7) td {
  grid-column: var(--6, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(8) td {
  grid-column: var(--7, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(9) td {
  grid-column: var(--8, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(10) td {
  grid-column: var(--9, 2) / var(--value, 2);
}

.chaarts.bar.waterfall tr:nth-of-type(11) td {
  grid-column: var(--10, 2) / var(--value, 2);
}