Flexibly Centering an Element with Side-Aligned Content
Published 2 years, 7 months pastIn a recent side project that I hope will become public fairly soon, I needed to center a left-aligned list of links inside the sides of the viewport, but also line-wrap in cases where the lines got too long (as in mobile). There are a few ways to do this, but I came up with one that was new to me. Here’s how it works.
First, let’s have a list. Pretend each list item contains a link so that I don’t have to add in all the extra markup.
<ol>
<li>Foreword</li>
<li>Chapter 1: The Day I Was Born</li>
<li>Chapter 2: Childhood</li>
<li>Chapter 3: Teachers I Admired</li>
<li>Chapter 4: Teenage Dreaming</li>
<li>Chapter 5: Look Out World</li>
<li>Chapter 6: The World Strikes Back</li>
<li>Chapter 7: Righting My Ship</li>
<li>Chapter 8: In Hindsight</li>
<li>Afterword</li>
</ol>
Great. Now I want it to be centered in the viewport, without centering the text. In other words, the text should all be left-aligned, but the element containing them should be as centered as possible.
One way to do this is to wrap the <ol>
element in
another element like a <div>
and then use
flexbox:
div.toc {
display: flex;
justify-content: center;
}
That makes sense if you want to also vertically center the list (with
align-items: center
) and if you’re already going to be
wrapping the list with something that should be flexed, but neither
really applied in this case, and I didn’t want to add a wrapper element
that had no other purpose except centering. It’s 2022, there ought to be
another way, right? Right. And this is it:
ol {
max-inline-size: max-content;
margin-inline: auto;
}
I also could have used width
there in place of
max-inline-size
since this is in English, so the inline
axis is horizontal, but as
Jeremy pointed out, it’s a weird clash to have a physical property
(width
) and a logical property
(margin-inline
) working together. So here, I’m going
all-logical, which is probably better for the ongoing work of retraining
myself to instinctively think in logical directions anyway.
Thanks to max-inline-size: max-content
, the list can’t
get any wider (more correctly: any longer along the inline axis) than
the longest list item. If the container is wider than that, then
margin-inline: auto
means the ol
element’s box
will be centered in the container, as happens with any block box where
the width is set to a specific amount, there’s leftover space in the
container, and the side margins of the box are set to auto
.
This is as if I’d pre-calculated the maximum content size to be (say)
434 pixels wide and then declared max-inline-size: 434px
.
The great thing here is that I don’t have to do that pre-calculation,
which would be very fragile in any case. I can just use
max-content
instead. And then, if the container ever gets
too small to fit the longest bit of content, because the ol
was set to max-inline-size
instead of just straight
inline-size
, it can fill out the container as block boxes
usually do, and the content inside it can wrap to multiple lines.
Perhaps it’s not the most common of layout needs, but if you find yourself wanting a lightweight way to center the box of an element with side-aligned content, maybe this will work for you.
What’s nice about this is that it’s one of those simple things that was difficult-to-impossible for so long, with hacks and workarounds needed to make it work at all, and now it… just works. No extra markup, not even any calc()
-ing, just a couple of lines that say exactly what they do, and are what you want them to do. It’s a nice little example of the quiet revolution that’s been happening in CSS of late. Hard things are becoming easy, and more than easy, simple. Simple in the sense of “direct and not complex”, not in the sense of “obvious and basic”. There’s a sense of growing maturity in the language, and I’m really happy to see it.