I have a flex container (the blue square) with the following properties:
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
Therefore, its children (the light blue squares) arrange themselves as you see below. However, I'd like to add another child (the green square) out of the normal flow and position it relative to its parent. To position it as you see below, I'd ideally write something like bottom: 20px;
and margin: auto;
.
I tried to play around with z-index
to no avail. How should I approach this? Should I resort to creating another parent element?
Below are five options for achieving this layout:
flex: 1
Apply position: relative
to the flex container.
Apply position: absolute
to the green flex item.
Now the green square is absolutely positioned within the flex container.
More specifically, the green square is removed from the document flow but stays within the bounds of the nearest positioned ancestor.
Use the CSS offset properties top
, bottom
, left
and right
to move the green square around.
flex-container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
position: relative;
border: 4px solid blue;
height: 300px;
width: 300px;
}
flex-container > flex-item:first-child {
display: flex;
}
flex-container > flex-item:first-child > flex-item {
border: 4px solid aqua;
height: 50px;
width: 50px;
margin: 0 5px;
}
flex-container > flex-item:last-child {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%); /* fine tune horizontal centering */
border: 4px solid chartreuse;
height: 50px;
width: 50px;
}
<flex-container>
<flex-item><!-- also flex container -->
<flex-item></flex-item>
<flex-item></flex-item>
<flex-item></flex-item>
</flex-item>
<flex-item></flex-item>
</flex-container>
One caveat: Some browsers may not completely remove an absolutely-positioned flex item from the normal flow. This changes the alignment in a non-standard, unexpected way. More details: Absolutely positioned flex item is not removed from normal flow in Firefox & IE11
With a combination of auto
margins and a new, invisible flex item the layout can be achieved.
The new flex item is identical to the bottom item and is placed at the opposite end (the top).
More specifically, because flex alignment is based on the distribution of free space, the new item is a necessary counterbalance to keep the three blue boxes vertically centered. The new item must be the same height as the existing green item, or the blue boxes won't be precisely centered.
The new item is removed from view with visibility: hidden
.
In short:
auto
margins to keep the blue boxes centered, with both green boxes creating equal balance from both ends.visibility: hidden
to the duplicate green box.flex-container {
display: flex;
flex-direction: column;
align-items: center;
border: 4px solid blue;
height: 300px;
width: 300px;
}
flex-container > flex-item:first-child {
margin-top: auto;
visibility: hidden;
}
flex-container > flex-item:nth-child(2) {
margin-top: auto;
display: flex;
}
flex-container > flex-item:last-child {
margin-top: auto;
margin-bottom: auto;
}
flex-container > flex-item:first-child,
flex-container > flex-item:last-child {
border: 4px solid chartreuse;
height: 50px;
width: 50px;
}
flex-container > flex-item:nth-child(2) > flex-item {
border: 4px solid aqua;
height: 50px;
width: 50px;
margin: 0 5px;
}
<flex-container>
<flex-item></flex-item>
<flex-item><!-- also flex container -->
<flex-item></flex-item>
<flex-item></flex-item>
<flex-item></flex-item>
</flex-item>
<flex-item></flex-item>
</flex-container>
This method is similar to #2, except it's cleaner semantically and the height of the green box must be known.
::before
.auto
margins to keep the blue boxes centered, with the green pseudo and DOM elements creating equal balance from both ends.flex-container {
display: flex;
flex-direction: column;
align-items: center;
border: 4px solid blue;
height: 300px;
width: 300px;
}
flex-container::before {
content: "";
margin-top: auto;
height: calc(50px + 8px); /* height + borders */
visibility: hidden;
}
flex-container > flex-item:first-child {
margin-top: auto;
display: flex;
}
flex-container > flex-item:last-child {
margin-top: auto;
margin-bottom: auto;
border: 4px solid chartreuse;
height: 50px;
width: 50px;
}
flex-container > flex-item:first-child > flex-item {
border: 4px solid aqua;
height: 50px;
width: 50px;
margin: 0 5px;
}
<flex-container>
<flex-item><!-- also flex container -->
<flex-item></flex-item>
<flex-item></flex-item>
<flex-item></flex-item>
</flex-item>
<flex-item></flex-item>
</flex-container>
flex: 1
to top and bottom itemsStarting with Method #2 or #3 above, instead of worrying about equal height for the top and bottom items to maintain equal balance, just give each one flex: 1
. This will force them both to consume available space, thus centering the middle item.
You can then add display: flex
to the bottom item in order to align the content.
This may be the cleanest and most efficient method. There is no need for absolute positioning, fake elements or other hackery.
Simply create a grid with three rows. Then center-align the items in the second and third rows. The first row can remain empty.
grid-container {
display: grid;
grid-template-rows: repeat(3, 1fr);
align-items: center;
justify-items: center;
border: 4px solid blue;
height: 300px;
width: 300px;
}
grid-item:nth-child(2) {
display: flex;
}
grid-item:nth-child(2)>flex-item {
width: 50px;
height: 50px;
margin: 0 5px;
border: 4px solid aqua;
}
grid-item:nth-child(3) {
border: 4px solid chartreuse;
height: 50px;
width: 50px;
}
<grid-container>
<grid-item></grid-item>
<grid-item><!-- also flex container -->
<flex-item></flex-item>
<flex-item></flex-item>
<flex-item></flex-item>
</grid-item>
<grid-item></grid-item>
</grid-container>