Character-based alignment

Extra

Earlier this week Silvestar Bistrović has been so kind to include one of my articles in his UI Dev Newsletter issue #106. Also in there, Darin Senneff shared his three most-wanted CSS table features. The third being column data alignment:

Unfortunately, there’s no native way to align data in a table column aside from basic left/right/center text alignment. If your data needs to align a certain way to be understood best, then your data’s presentation may be unclear.

Darin also mentions a proposed solution exists:

/* Section 7.2. "Character-based Alignment in a Table Column"
   in the CSS Text Module Level 4 draft specification:
   Using a string as a value to the text-align property.
*/
td {
	text-align: "." right;
}
/* Browser support by the end of 2024: ❌ */

This would align numbers by the decimal point. Alas, there’s not one browser that supports this syntax.

But we can work around this! Eric Meyer came up with a really advanced solution using CSS transforms. If you’re looking for something simpler, albeit less versatile, I may have something in store for you.

Sadly the HTML needs to do some heavy lifting. On the upside, it degrades perfectly if the CSS doesn’t load. 🙂

Let’s work our way towards the solution.

Adding padding

We pad our numbers with trailing zeros, and at the same time wrap the padded part inside a new element. This works for any number of digits in the fractional part, but here’s how it looks like if we want two digits after the decimal point:

<!-- Nothing to do if the value already has
     two digits after the decimal point: -->
<td>
	1.23
</td>

<!-- Adding one trailing zero: -->
<td>
	1.2<span aria-hidden="true">0</span>
</td>

<!-- For a value without a fractional part, the
     decimal point is part of the add-on: -->
<td>
	1<span aria-hidden="true">.00</span>
</td>

The aria-hidden attribute ensures that screen readers don’t announce the suffix as a second value.

To align such data in a table column, for starters we need right-aligned cells, and our digits all have to be the same size, i.e. font-variant-numeric should give us tabular numbers.

To state the obvious, we always have to fill up to the same number of digits, but once we have this in place, hiding the <span> without changing layout is all we need to do. This is where visibility: hidden comes in:

td {
	font-variant-numeric: lining-nums tabular-nums;
	text-align: right;
}
td [aria-hidden] {
	visibility: hidden;
}

Done. At least in it’s simplest form.

Adding a unit

If our value should also have a unit at the end, we’ve got more work to do. We’ll add the unit as the last thing in our cell. Additionally, we now need a wrapper for the whole cell content, because we need to bring in flexbox, and we need the table cell to remain at display: table-cell, so we’ll add another <span> to do the flexing.

<!-- with unit at the end (e.g. "%"), and wrapped
     inside another `span` element: -->
<td>
	<span>
		1<span aria-hidden="true">.00</span>%
	</span>
</td>

Inside our flexbox wrapper, we use order: 1 to move the hidden suffix after the unit:

td > span {
	display: inline-flex;
}
td [aria-hidden] {
	visibility: hidden;
	order: 1;
}

Done.

Unless you want to add even more creative styling (like I did in my CodePen). Eventually you may even need a wrapper for the unit:

<!-- with wrapped unit: -->
<td>
	<span>
		1<span aria-hidden="true">.00</span>
		<abbr title="percent">%</abbr>
	</span>
</td>

Check out my CodePen example for all the details. It even contains a teeny bit of interactivity.