CSS is hard, so they say. But have you tried CSS while using a framework that messes up your DOM? Because that's where the real fun lies.
The problem
So I am working on a simple mobile interface: header, content, footer. The header and the footer have a fixed size, and I want the content to be placed at the center of the remaining space.
"Ah, flexbox, my dear old friend, we meet again!" ― Myself (not really, it's just for the storytelling)
So of course I come up with this simple implementation:
<body>
<header></header>
<main></main>
<footer></footer>
</body>
body {
display: flex;
flex-direction: column;
justify-content: space-between;
}
So I'm happy, as any front-end developer is when things align nicely.
But as the application grows, I realize that I'm gonna need to wrap the main
and the footer
elements inside a single Angular component, for structural reasons inherent to the application.
That's when my beautiful page breaks, and you can see why by looking at the generated markup taking in account my new component:
<body>
<header></header>
<my-component>
<main></main>
<footer></footer>
</my-component>
</body>
Alas! Flexbox doesn't care for anyone but its own direct children, which are now header
and my-component
.
My first thought was to make my-component a flex container itself, but I couldn't get very far with this idea.
my-component {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
Now, take a moment: how would you do it?
A dirty way
I know you didn't take a moment and read this right away. It's okay.
One way I didn't think of before writing this article is to add a pseudo element to my-container
:
my-component::before {
content: '';
}
my-container
has now three children: the before
pseudo element, the main
bloc and the footer
bloc.
You can see it in action here!
That's it! Sometimes a dirty fix like this one is so simple that it becomes quite attractive…
A cleaner way
But adding a pseudo element just to trick the flexbox system does not feel very clean.
A few weeks ago I read this great article by Rachel Andrews: Digging Into The Display Property: Box Generation.
While reading about the display: contents
property, I had two conflicting thoughts:
- "Well now, that's very interesting"
- "Yep, never gonna use that though"
But working on my layout problem, I thought about it again. From the article:
"The value display: contents will remove the box it is applied to from the box tree in the same way that display: none does, but leave the children in place."
In other words, it will result in a situation similar (in our case*) to what would happen if we commented out the element (but not its children):
<body>
<header></header>
<!-- <my-component> -->
<main></main>
<footer></footer>
<!-- </my-component> -->
</body>
And this is how you actually do it:
my-component {
display: contents;
}
*In reality, CSS properties applied on my-component
still impact its children (font-size
, color
...). So not exactly like commenting out the element, but you get the idea.
So… Does it mean that flexbox-wise, main
and footer
become direct children of body again? Yes! my-component
doesn't have to be a flex container itself anymore. The justify-content: space-between
applied to the body
now works again.
This is the shortest solution presented in this article: one selector and one property. Almost magical.
While I love the beautiful simplicity of this solution, display: contents
has its drawbacks.
There can be accessibility problems with display: contents
, because it hides the element from the accessibility tree. If you use it on a ul
element in order to achieve some layout, the information that this is a list will be lost in many browsers.
This is not a problem in our case though, since my-component
doesn't carry a specific semantic meaning. But one could be concerned about browser support, which is in that grey area that I would call the "meh" area.
A cleaner cleaner way?
Enter the margin: auto
technique.
It's only while writing this article that I remembered about another article, one of the greatest I read on this subject: Flexbox's Best Kept Secret, by Sam Provenza. It's from 2015 and I used this "secret" countless times since then.
4 years later, I'm not sure this is a well-known fact. But you can use margin-left: auto
, for exemple, in order to "push" a flexbox item to the right as much as possible.
That's super useful ! With this technique, you can push a flex item in any direction, horizontal or vertical.
But there was a part of the article that I had forgotten:
"If you don't specify a direction, simply applying margin: auto, a flex item would evenly distribute any extra space on either side of the itself equally"
So the "secret" also works with multiple auto
margins.
And there it lies, a wonderful solution to our problem: make my-component
a flex container again, and use margin: auto 0
on the main
element.
my-component {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
main {
margin: auto 0;
}
See it here!
No magical pseudo element. Less accessibility and compatibility risks. Just sweet and glorious flexpower™.
The journey of vertical centering never stops.
Top comments (0)