Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make flex items stretch full height and vertically center their content

Tags:

css

flexbox

I have a horizontal flex box (i.e. flex-direction: row, i.e. side-by-side) with a few items. Each item can be a single line of text, or can have multiple lines. I want to vertically-align the contents of each flex item.

If each item had a transparent background, I could easily use align-items: center. However, I want each item to be stretched vertically, because I want to set a background (or maybe borders, or maybe it is a clickable region) to the entire available height.

So far, I know:

  • Stretching: align-items: stretch
  • Aligning: align-items: center
  • Stretching and aligning: ???

Demo available at http://codepen.io/denilsonsa/pen/bVBQNa

Screenshot of the demo

ul {
  display: flex;
  flex-direction: row;
}
ul.first {
  align-items: stretch;
}
ul.second {
  align-items: center;
}
ul > li {
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 5em;
  text-align: center;
}
ul > li:nth-child(2) {
  background: #CFC;
}

/* Visual styles, just ignore. */
html, body { font-family: sans-serif; font-size: 25px; }
ul, li { list-style: none; margin: 0; padding: 0; }
ul { background: #CCF; width: 25em; }
<ul class="first">
  <li>Sample</li>
  <li><span>span</span></li>
  <li><span>multiple</span> <span>span</span></li>
  <li>text <span>span</span></li>
  <li>multi<br>line</li>
</ul>
<hr>
<ul class="second">
  <li>Sample</li>
  <li><span>span</span></li>
  <li><span>multiple</span> <span>span</span></li>
  <li>text <span>span</span></li>
  <li>multi<br>line</li>
</ul>

Similar questions:

  • Question 14012030 and question 23442692 and question 27729619 and question 25311541 ask essentially the same thing, but they either have a single element or plain text as child of each flex item. As soon as we have mixed content, possibly with multiple elements, those solutions do not work.
  • Question 19026884 is unrelated, the issue there was the wrong markup.
like image 461
Denilson Sá Maia Avatar asked Sep 28 '15 17:09

Denilson Sá Maia


People also ask

How do you vertically center a flex item?

By default flex items will fill vertical space of their parent element. To vertically center our div we can add a single CSS property. By using align-items: center we can vertically center all flex items to the parent container along the cross axis of the flex container.

How do I vertically center a div with Flex?

To center a div vertically and horizontally using flexbox, you need to wrap the div or div's inside a container with properties ' display: flex; flex-direction: column; justify-content: center;align-items: center; ', then just make the div ' text-align: center; ' if it has text.

Which flexbox property aligns items vertically?

We use justify-content to align the item on the main axis, which in this case is the inline axis running horizontally. You can take a look at the code of this example below. Change the size of the container or nested element and the nested element always remains centered.

How do I make my flexbox 100% height?

Getting the child of a flex-item to fill height 100%Set position: absolute; on the child. You can then set width/height as required (100% in my sample).


2 Answers

Unfortunately, it is impossible to achieve the desired effect while using the exact markup posted in the question.

The solution involves:

  • Setting display: flex; on <li>.
  • Wrapping the <li> contents into another element.
    • This is required because <li> is now a flex container, so we need another element to prevent the actual contents from becoming flex items.
    • In this solution, I introduced a <div> element, but it could have been other element.
  • Now that <li> is a flex container and it contains only a single child, we can use align-items and/or justify-content to align this new and only child.

The DOM tree looks like this:

<ul> flex-parent, direction=row
 ├ <li> flex-item && flex-parent && background && JavaScript clickable area
 │  └ <div> flex-item as a single transparent element
 │     ├ Actual contents
 │     └ Actual contents
 ├ …

Note: The solution in this answer uses 2 nested flex boxes. The solution by Michael_B uses 3 nested flex boxes, because it has the added challenge of expanding the <a> element to fill the entire <li>. Which one is preferred depends on each case. If I could, I would accept both answers.

/* New code: this is the solution. */
ul > li {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

/* Old code below. */
ul {
  display: flex;
  flex-direction: row;
  align-items: stretch;
}
ul > li {
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 5em;
  text-align: center;
}
ul > li:nth-child(2) {
  background: #CFC;
}

/* Visual styles, just ignore. */
html, body { font-family: sans-serif; font-size: 25px; }
ul, li { list-style: none; margin: 0; padding: 0; }
ul { background: #CCF; width: 25em; }

button:focus + ul {
    font-size: 14px;
    width: auto;
}
<button>Click here to set <code>width: auto</code> and reduce the font size.</button>

<!-- New code: there is a single <div> between each <li> and their contents. -->
<ul>
  <li><div>Sample</div></li>
  <li><div><span>span</span></div></li>
  <li><div><span>multiple</span> <span>span</span></div></li>
  <li><div>text <span>span</span></div></li>
  <li><div>multi<br>line</div></li>
</ul>
like image 94
Denilson Sá Maia Avatar answered Oct 15 '22 14:10

Denilson Sá Maia


I want each item to be stretched vertically, because I want to set a background (or maybe borders, or maybe it is a clickable region) to the entire available height.

You can achieve this layout without any changes to your HTML structure. There's no need for additional containers.

You already have a primary flex container and a group of flex items. Simply make those flex items into nested flex containers. That will enable you to align the content with flex properties.

(Since you mentioned that you may need clickable regions, I switched from li to a elements.)

nav {
  display: flex;
  background: #CCF;
  width: 25em;
}

nav > a {
  flex: auto;  /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
  text-align: center;
  text-decoration: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
nav > a:nth-child(2) {
  background: #CFC;
}

html, body {
  font-family: sans-serif;
  font-size: 25px;
}
<nav>
  <a href="">Sample</a>
  <a href=""><span>span</span></a>
  <a href=""><span>multiple</span> <span>span</span></a>
  <a href="">text <span>span</span></a>
  <a href="">multi<br>line</a>
</nav>

revised codepen

Note that content placed directly inside a flex container is wrapped in an anonymous flex item:

From the spec:

4. Flex Items

Each in-flow child of a flex container becomes a flex item, and each contiguous run of text that is directly contained inside a flex container is wrapped in an anonymous flex item.

So, because the text is automatically wrapped in flex items, you can keep the full height of each item (align-items: stretch from the primary container) and vertically center the content (align-items: center from the nested containers).

like image 45
Michael Benjamin Avatar answered Oct 15 '22 13:10

Michael Benjamin