Making room for long list markers with subgrid

The problem

When working with lists, it’s typical for list markers like numbers or bullets to overhang the list item content. On the web, lists (<ul> and <ol>) accomplish this with fixed padding — 40px by default.

Screenshot of a list numbered 1–6 with the numbers in 40px of fixed padding. Padding is visible in purple thanks to dev tools.

Most of the time this is fine, but sometimes markers can get quite wide. Here, I’m starting at 500 and counting up in steps of 500. 40px is clearly not enough space.

Screenshot of a list numbered 500–3500 with the numbers overflowing 40px of fixed padding. Padding is visible in purple thanks to dev tools.

Traditionally, you need to make room by setting that padding to some wider value. For example, in her 2019 Smashing Mag article on CSS lists, Rachel Andrew added the word “Step” to numeric counters, then set padding-inline-start to 4em to account for the extra width. But that has magic number vibes, and only works if you know in advance how long your markers will get.

The solution

CSS subgrid to the rescue! Subgrid is a relatively new CSS feature that, as of September 2023, has landed in all major browsers[1]. In short, it allows a grandchild of a grid container to participate in its grandparent’s grid.

For our case, the concept is to make the list itself a grid container with two columns: one for the markers, and one for the list item content. Then, set each list item element to span both of those columns, but have its children — the marker and the <li>’s non-pseudo content — get subgrid-ed into the list’s two columns.

Screenshot of a list numbered 500–3500 with the numbers in a perfect-width column. Grid lines are visible thanks to dev tools.

Start by setting up the <ol> as a grid container. The column for the marker is minmax(2.5rem, max-content) wide, meaning it’s always at least the browser default (2.5rem == 40px at default zoom) but can grow to be as wide as the widest marker content without any wrapping. The column for everything else takes up the rest of the space.

ol {
/* don't need the default arbitrary padding */
padding-inline: 0;

display: grid;
/* first column is the marker pseudoelement */
grid-template-columns: minmax(40px, max-content) 1fr;
}

Next, the <li> needs to span both of the columns we just set up. We do that by setting grid-column to 1 / -1, which tells the browser to place the <li> starting at the first column grid line and ending at the last (-1) column grid line. Then, make it a grid container itself whose columns are, thanks to subgrid, the <ol>’s columns.

li {
/* the whole list item covers both columns. then *its* children (the marker and regular <li> content) are subgrid-ed into the parent's grid */
grid-column: 1 / -1;

display: grid;
/* use the parent grid's columns. neat! */
grid-template-columns: subgrid;

/* for demo purposes, let's see some wide numbers! */
counter-increment: list-item +500;
}

Now, there’s a bit of a catch: we just set the <li> to display: grid, which means it’s no longer display: list-item[2]. That means we lose the ::marker pseudoelement and have to use a ::before. That’s not a big deal, but it does mean we have to set it up ourselves:

li::before {
content: counter(list-item)".";
text-align: right;
}

One final thing is that, about a year ago, Šime Vidas wrote a very in-depth article about the space after a list marker. Using this subgrid technique introduces another possibility that feels very clean to me: we can add a gap after the list marker simply by setting the column-gap property in the parent <ol> grid.

ol {
/* ✂️ */
column-gap: .5ch;
/* ✂️ */
}

Neat, an auto-sizing marker column with no magic numbers!

See the Pen Subgrid for list markers by Noah (@noleli) on CodePen.


  1. Be sure to check availability before deciding to use it for your application. ↩︎

  2. It’d be neat if one day display’s two-value syntax supported list-item, so it could be display: list-item grid. ↩︎