Let me show you a way I recently discovered to center a bunch of elements around what I call the pivot. I promise you that funky HTML is out of the question and you won’t need to know any bleeding-edge CSS to get the job done.
I’m big on word games, so I recently re-imagined the main menu of my website as a nod to crossword puzzles, with my name as the vertical word, and the main sections of my website across the horizontals.
Here’s how the design looks with the names of some colors instead:
And here’s a sample of the HTML that drives this puzzle:
<div class="puzzle">
<div class="word">
<span class="letter">i</span>
<span class="letter">n</span>
<span class="letter">d</span>
<span class="letter">i</span>
<span class="letter pivot">g</span>
<span class="letter">o</span>
</div>
<!-- MORE WORDS -->
</div>
In this example, the letter g
is the pivot. See how it’s not at the halfway mark? That’s the beauty of this challenge.
We could apply an offset to each word using hard-coded CSS or inline custom properties and walk away. It certainly gets an award for being the most obvious way to solve the problem, but there’s a downside — in addition to the .pivot
class, we’d have to specify an offset for every word. The voice in my head tells me that’s adding unnecessary redundancy, is less flexible, and requires extra baggage we don’t need every time we add or change a word.
Let’s take a step back instead and see how the puzzle looks without any balancing:
Imagine for a moment that we use display: none
to hide all of the letters before the pivot; now all we can see are the pivots and everything after them:
With no further changes, our pivots would already be aligned. But we’ve lost the start of our words, and when we reintroduce the hidden parts, each word gets pushed out to the right and everything is out of whack again.
If we were to hide the trailing letters instead, we’d still be left with misaligned pivots:
All of this back-and-forth seems a bit pointless, but it reveals a symmetry to my problem. If we were to use a right-to-left (RTL) reading scheme, we’d have the opposite problem — we’d be able to solve the right side but the left would be all wrong.
Wouldn’t it be great if there was a way to have both sides line up at the same time?
As a matter of fact, there is.
Given we already have half a solution, let’s borrow a concept from algorithmics called divide and conquer. The general idea is that we can break a problem down into parts, and that by finding a solution for the parts, we’ll find a solution for the whole.
In that case, let’s break our problem down into the positioning of two parts. First is the “head” or everything before the pivot.
Next is the “tail” which is the pivot plus everything after it.
The flex
display type will help us here; if you’re not familiar with it, flex
is a framework for positioning elements in one-dimension. The trick here is to take advantage of the left and right ends of our container to enforce alignment. To make it work, we’ll swap the head and tail parts by using a smaller order
property value on the tail than the head. The order property is used by flex to determine the sequence of elements in a flexible layout. Smaller numbers are placed earlier in the flow.
To distinguish the head and tail elements without any extra HTML, we can apply styles to the head part to all of the letters, after which we’ll make use of the cascading nature of CSS to override the pivot and everything after it using the subsequent-sibling selector .pivot ~ .letter
.
Here’s how things look now:
Okay, so now the head is sitting flush up against the end of the tail. Hang on, don’t go kicking up a stink about it! We can fix this by applying margin: auto
to the right of the last element in the tail. That just so happens to also be the last letter in the word which is now sitting somewhere in the middle. The addition of an auto margin serves to push the head away from the tail and all the way over to the right-hand side of our container.
Now we have something that looks like this:
The only thing left is stitch our pieces back together in the right order. This is easy enough to do if we apply position: relative
to all of our letters and then chuck a left: 50%
on the tail and a right: 50%
on our head items.
Here’s a generalized version of the code we just used. As you can see, it’s just 15 lines of simple CSS:
.container {
display: flex;
}
.item:last-child {
margin-right: auto;
}
.item {
order: 2;
position: relative;
right: 50%;
}
.pivot, .pivot ~ .item {
order: 1;
left: 50%;
}
It’s also feasible to use this approach for vertical layouts by setting the flex-direction
to a column
value. It should also be said that the same can be achieved by sticking the head and tail elements in their own wrappers — but that would require more markup and verbose CSS while being a lot less flexible. What if, for example, our back-end is already generating an unwrapped list of elements with dynamically generated classes?
Quite serendipitously, this solution also plays well with screen readers. Although we’re ordering the two sections backwards, we’re then shifting them back into place via relative positioning, so the final ordering of elements matches our markup, albeit nicely centered.
Here’s the final example on CodePen:
Conclusion
Developers are better at balancing than acrobats. Don’t believe me? Think about it: many of the common challenges we face require finding a sweet spot between competing requirements ranging from performance and readability, to style and function, and even scalability and simplicity. A balancing act, no doubt.
But the point at which we find balance isn’t always midway between one thing and another. Balance is often found at some inexplicable point in between; or, as we’ve just seen, around an arbitrary HTML element.
So there you have it! Go and tell your friends that you’re the greatest acrobat around.
You are amazing!!!!!!!!
This is awesome!
This is pretty goddamn impressive. I’m just imagining the many lines of JS people would reach for to achieve this.
Bravo!
With a drop of vinegar: it would be perfect if some letters weren’t positioned outside the containing
.puzzle
block element. Especially for a horizontal pivot, this could be a problem: https://codepen.io/ccprog/pen/XWKJpxjDo you see a way to solve this?
Thanks! Yes, in certain situations the contents leak out of their container. Both of the ways I’ve used the puzzle I haven’t had to worry about this effect:
That said, there are several ways to mitigate it. The easiest one is to ensure that the top-level container never drops below a certain width (or height, in your case). Here’s a copy of your pen with the font-size scaled down and a fixed height.
Wow! Simple, elegant, wise: brilliant.
Wow, Julian! Very neat lateral thinking here.
out of box approach!!! especially with “order” and “margin” for alignment.
Super innovative and out of the box!
Ha, nice one, I did a quick experiment on those lines some time ago to balance nav items around a logo:
Good work! :D
Wow, i thought it would be so complicated. It turns out mind-blowing in the end. Really cool approach, Julian!
Thank you for writing this! Got me thinking if Grid would be easier so tried it on this pen.
Thought I could do it without the spans, cheating with monospaced fonts, letter spacing, paddings, etc.. but realized I still wouldn’t be able to target the specific letter on the grid (nth-letter selector where are you!!) so I went back to span all the things.
Then did this second pen.
Trying to target the area not the letter, that way you can do some more fun stuff with hovers and that one turned out a bit cleaner and more fun. Thanks again!
Wow, great approach. Really mindblowing. I am trying to figure out how to align my pivot on the left not on the center of the page. Maybe you´ve got a hint?
Thanks!
From what I understand, you still want the pivots to be aligned, but you want them sitting up against the left side of the container? In that case, you can set the positioning of the head and tail like so:
Just be aware that the original container will still be its original size and the items to the left of the pivot will be positioned outside it, so you might want to compensate by adding a margin.
I hope that helps!
Thanks for writing this!!
Had some fun trying it with Grid:
[Attempt 1](https://codepen.io/jupago/pen/MWeKoaV)
[Attempt 2](https://codepen.io/jupago/pen/YzWwQRG)
For attempt one I was trying to be too clever and not use any spans, but since we still don’t have nth-letter (╯°□°)╯︵ ┻━┻ I could not make it work.
For attempt 2 I was able to take advantage of Grid and target the Cell NOT the letter, this one is definitely more fun. (hover!)
(*wrote a similar reply a couple of days ago but it looks like it didn’t make it through so here’s a redo hoping I don’t double reply!)
Nice one! I’m digging the hover effect on the second one! Really glad you had fun playing around with it too!! :)