Over the years I've found several design patterns that commonly crop up, but which lack clear, bulletproof solutions using only CSS or HTML. I've come to think of these as my "holy grail" patterns – a reference to the OG "holy grail layout" that was all the rage back in the early days of my web dev career, and which now threatens to date me horribly 😅 There are several patterns on that list, but the one that most recently reared its ugly head I've come to refer to as "diamond justification".
Picture a typical card layout:
- Some title text;
- A paragraph or two of blurb;
- Maybe an image;
- And probably a CTA of some kind; a link, or button.
Now centre align everything in that card, and consider how the title text looks. It's probably line-wrapped – likely a couple of times – and so we want to evenly distribute the visual weight of that text as pleasingly as possible. When this happens, what I tend to get from designers will be a narrow first line, followed by a wider second line, and finally another narrow third line. Something like this:
First Line Second Line of Text And Third Line
(hopefully the white space in the above code block is preserved when this is published 🤞)
It looks a lot like typical centred text, except the first and last lines are always the shortest, and then each subsequent line from the top is slightly longer, with the middle line being the longest, before they slowly get shorter again. Y'know, a bit like a diamond: 💎
text-align: center is all that you need. Stick that in your CSS for your card header or heading text and you'll be golden 👌 That is, right up until someone creates a new card with a really long first word, or last word, or something else that upsets the arcane gods who control where centred text decides to position linebreaks – because damned if I can work that formula out! – and then you get some top- or bottom-heavy visual mess. Eurgh 🤮
Diamond justification is one of those visual nice-to-haves which crops up all of the time. It's just pleasing to look at, and we're all used to it from print. As a result, I'm confident in claiming that I've been handed a design using this pattern at some point in each of my last three jobs, and probably not just once per employer, either. And yet, I've never seen a good implementation in the wild or any articles trying to teach me that one great trick I need to actually get it working.
What about <br>, bruh?
Yeah, fine, you can achieve this effect using centred text and a liberal sprinkling of
<br> elements. Chuck a string that looks like:
First Line <br> Second Line of Text <br> And Third Line
into any layout, apply text centring, and that'll just work. Simples, as the meerkat would say (why is there no meerkat emoji 😠).
But this fails for exactly the same underlying reason as regular centre alignment: dynamic content. Most content on the web is being dynamically pulled into a UI via some kind of content management system or API. And no matter how much we developers wish it to be the case, content editors do not have the time (or often inclination) to add linebreaks to every new card or article headline or whatever. Nor, frankly, should they have to. This problem is pure visuals; there shouldn't be any need for human input here. And in many cases, inserting linebreaks into WYSIWYG text editors is non-trivial. I use CraftCMS on this here website, and whilst I'll defend its content editorial process as one of (if not the) best in the industry, adding linebreaks into text that I can guarantee will not get stripped back out is not something I'd ever feel comfortable making a part of a process for another person.
No, linebreaks are okay for fixed headings that are never going to change – and somehow magically also don't reflow as you change between different screen sizes 😬 – but they are a solution of last resort and simply do not work in the majority of use cases.
So what do I propose?
shape-outside rule is not exactly new, but it often appears to be overlooked. At its core,
shape-outside creates a section of negative space that text cannot overlap. Normally, this is used to wrap text around another element – often an image – but there's nothing stopping you from using it to just bend text into a specific layout.
Of course, it would be great if we could literally do that: define the text area of an element as a diamond shape (for example) and have the text arrange itself within those boundaries. Unfortunately, that's not quite how this works. The outside part of the name is the clue:
shape-outside tells text where not to go, not where it should go. It creates an area that is out of bounds, so to speak, outside of the text. So we can't just create a diamond and fill it; instead, we have to block off an area so that the only available space left happens to look like a diamond. Our shape needs to be the inverse or imprint of a diamond.
What, precisely, isn't a diamond?
Given that our text area is a rectangle, we need to create a shape that leaves 100% of the width available at the very centre, and then grades out towards both top and bottom at the same rate. Effectively, we need to place triangles in each of the corners, to create something that looks like this:
We could make four separate triangles, but it would be simpler to create two polygons, one for each side of our text area. This is a fairly handy solution, because it means we can achieve our desired shape with two HTML elements, so positioning can be handled with floats alone. That's important, because one fun little quirk of
shape-outside is that it only works on floated elements. I'm not sure why – as it's a massive limitation – but by the looks of things the original spec was dreamed up back when floats were still commonplace, so perhaps it was considered the most likely use-case at the time? At any rate, this limitation results in a number of caveats and issues with this technique which I'll discuss below; limitations that would be solvable using other positioning techniques that are normally available in CSS 😒
For now, though, all we can do is float, and that means we can float left, and we can float right, so if we can achieve our desired negative space with only one or two shapes, we don't need to worry about additional wrapping elements or other complex layout tricks. Instead, we can just create two dummy elements, float one to each side, and set our necessary CSS. That ends up looking like this:
(PS if you scroll down, you can see an example of the same text that has just been centred – resulting in a top-heavy layout – as an example of what I'm trying to avoid, and also as proof that this technique is having the desired effect 😉 PPS if you try to open the HTML or CSS tabs, you'll get a good idea of some of the limitations of this technique, too 😅)
The outcome is pretty darn close! Throw on some padding, tweak a few heights and widths, and we have a pretty convincing piece of diamond justification 🙌 It scales well with both text and screen zoom; it accepts a large variety of phrases whilst behaving predictably; and you'll never get top- or bottom-heavy results. If the text is too little to wrap, you may not get a diamond, but it will still follow the overall pattern and provide a visually pleasing outcome.
Just don't resize your screen. Or overflow the available space. Or change the container size too much (and definitely don't change it non-linearly).
Like I said earlier: there are some caveats and issues 😬
Here be dragons
Issue the first: floated content does not know how large its container is. This means your floated shape cannot use percentage heights or widths, so we cannot tell it to take up 100% of the height. As a result, if your container changes shape or aspect ratio dramatically – say because a user is on a smaller screen than yours – then your floated negative space will stop being as effective, or outright break the layout entirely. This issue can be worked around, but it requires a lot of testing and a lot of media query breakpoints – not great 😒 The doubly-annoying thing is that absolute positioning would definitely solve this, but you can't position a float (for valid reasons), so that's out.
Issue the second: even if you can tweak your shape's height, width, and position into all the possible arrangements necessary to make the alignment responsive across any viewport size, you still have the problem of dynamic content. Sure, this solution is a lot more resilient than options like centred text, but it can still break. Add too much content and your text area will become larger than your shape, resulting in words spilling underneath the defined area, which looks pretty bad.
There are some possible stop-gap answers to this issue:
- Have a character limit that will never exceed your predefined height(s). This is doable, but does make for a lot of edge-case testing if you are also keeping the layout responsive (as you should be), and obviously isn't going to win you any fans amongst the editorial team when their preferred title is one or two characters too large.
Unfortunately, there are other edge cases which feel a little trickier to work around:
- If the container ends up being larger than the available viewport space, then your shapes can squeeze the text into some strange layouts;
- The width of the floated shapes is defining a lot of the layout, so you can hit some odd issues as word lengths increase/decrease;
- And, speaking of, particularly long words need special handling in terms of breaking them to fit, but that can cause a lot of readability issues – the alternative results in some distinctly odd alignments, and a much higher risk of hitting the issues mentioned above.
All of these are definitely niche edge cases and, in my experience, they've only actually arisen during QA stress testing of UIs, rather than actual real-world user interactions, but it's definitely one of those "your mileage may vary" cases which will heavily depend on how you're using this pattern in your own designs.
The quest continues
All of which is a long-winded way of saying that I haven't solved this particular riddle. I really thought I had, until we began experimenting with my "solution" in a broader range of scenarios, at which point its limitations became readily apparent. However, this is still a positive step, and one I felt worth writing up, if for no other reason than the next time I'm handed a design with this pattern in place, I can start from a more solid foundation (and see why some of the "obvious" ideas don't actually work). That's kind of the whole point of having a blog, really, isn't it 😅