9

I am implementing css-only tabs with hash-tag linking. I'm very very close but can't quite get the flex wrapping to work properly. In order for everything to work they way I want it to with :target (I've done this before with radio buttons and that gives a bit more flexibility), I need all tabs and all sections at the same level so I have:

body
  section
  anchor
  section
  anchor
  ...

I can then use flexbox ordering to make all anchors appear first and style the appropriately, set all sections to width 100% and use flex-wrap to allow them to wrap to the next line. The problem is that I seem to be unable to control the height of the first row. What's going on here?

body {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  height: 100vh;
  outline: 1px solid green;
}
body > a {
  padding: 10px;
  margin: 0 5px;
  order: -1;
  flex-grow: 0;
  border: solid gray;
  border-width: 1px 1px 0 1px;
}
body > a:last-of-type {
  order: -2;
}
section {
  flex-grow: 1;
  width: 100%;
  background-color: cornsilk;
  display: none;
  border-top: 1px solid grey;
}
section:last-of-type {
  display: flex;
}
a {
  font-weight: normal;
}
a:last-of-type {
  font-weight: bold;
}
:target {
  display: flex;
}
:target ~ section {
  display: none;
}
:target ~ a {
  font-weight: normal;
}
:target + a {
  font-weight: bold;
}
<section id="advanced">
 Advanced Stuff
</section>
<a href="#advanced">Advanced</a>

<section id="home">
 Home Things
</section>
<a href="#home">Home</a>

Slightly easier to play with jsbin

The issue is that the tabs in the first row stretch to the height of the visible section rather than collapsing to the height of their contents. Even a specific height and max-height seems to be ignored.

Please note, the question is specifically about line heights when wrapping with flexbox. I know a million different ways to build tabs both in css and js. I'm specifically looking for a deeper understanding of flex-wrap.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • So what exactly do you want to happen when the tabs wrap? Maybe you could illustrate your desired outcome. – Michael Benjamin May 31 '16 at 19:24
  • @Michael_B run the example - you see how they're stretching to be the same height as the section rather than collapsing to the size of their contents? I want them to look like tabs but instead they're all stretched out. I want both rows to size to their contents – George Mauer May 31 '16 at 19:25
  • Two initial settings of a flex container are `align-items: stretch` and `align-content: stretch`. You can override this by changing the value to `flex-start`. – Michael Benjamin May 31 '16 at 19:28
  • @Michael_B `align-content` seems to get me very close but that for some reason makes the `section` no longer fill down vertically. – George Mauer May 31 '16 at 19:33
  • So you want `stretch` on wide screens and `flex-start` on narrow screens? – Michael Benjamin May 31 '16 at 19:38
  • @Michael_B no, I just want the visible section to flex and occupy all available space which it doesn't if you give the container `align-content: flex-start`. [See this example](http://jsbin.com/kufele/5/edit?html,css,output) note how the section with the tan background doesn't stretch to the bottom, even though the flex container *does* and the section has `flex-grow: 1`. Honestly, I"m not sure I understand why `align-content` would do that. – George Mauer May 31 '16 at 19:43

3 Answers3

14

Two initial settings of a flex container are align-items: stretch and align-content: stretch. You can override this by changing the value to flex-start.

But that seems to solve only part of the problem. You also want selected wrapping items to stretch the full height of the container.

This isn't happening because when flex items wrap they take only the minimum size necessary to contain their content.

From the flexbox spec:

6. Flex Lines

In a multi-line flex container (even one with only a single line), the cross size of each line is the minimum size necessary to contain the flex items on the line (after alignment due to align-self), and the lines are aligned within the flex container with the align-content property.

In other words, when there are multiple lines in a row-based flex container, the vertical height of each line (the "cross size") is the "minimum size necessary to contain the flex items on the line".

To make your layout work you would need a combination of row- and column-direction flex containers, along with an adjustment to the HTML.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
1

Remove the height: 100vh from body and flex-grow: 1; from section

What happens is you set the body to be full height and then tell the section to grow full available space, hence they fill the browser height.

body {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  outline: 1px solid green;
}
body > a {
  padding: 10px;
  margin: 0 5px;
  order: -1;
  flex-grow: 0;
  border: solid gray;
  border-width: 1px 1px 0 1px;
}
body > a:last-of-type {
  order: -2;
}
section {
  width: 100%;
  background-color: cornsilk;
  display: none;
  border-top: 1px solid grey;
}
section:last-of-type {
  display: flex;
}
a {
  font-weight: normal;
}
a:last-of-type {
  font-weight: bold;
}
:target {
  display: flex;
}
:target ~ section {
  display: none;
}
:target ~ a {
  font-weight: normal;
}
:target + a {
  font-weight: bold;
}
<section id="advanced">
 Advanced Stuff
</section>
<a href="#advanced">Advanced</a>

<section id="home">
 Home Things
</section>
<a href="#home">Home</a>

Sample 2, using a wrapper

body {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  outline: 1px solid green;
  height: 100vh;
}
body > div {
  flex: 0;
  order: 1;
}
section {
  flex: 1;
  background-color: cornsilk;
  display: none;
  border-top: 1px solid grey;
  order: 2;
}
body > div > a {
  display: inline-block;
  padding: 10px;
  margin: 0 5px;
  border: solid gray;
  border-width: 1px 1px 0 1px;
}
section:last-of-type {
  display: flex;
}
a {
  font-weight: normal;
}
a:first-of-type {
  font-weight: bold;
}
:target {
  display: flex;
}
:target ~ section {
  display: none;
}
:target ~ div a {
  font-weight: normal;
}
#home:target ~ div a[href='#home'],
#advanced:target ~ div a[href='#advanced'] {
  font-weight: bold;
}
<section id="advanced">
 Advanced Stuff
</section>

<section id="home">
 Home Things
</section>

<div>
  <a href="#home">Home</a>
  <a href="#advanced">Advanced</a>
</div>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • Well...yes, but removing `height: 100vh` fundamentally changes what it does. The section will no longer expand to the bottom. In practice that would probably be a `min-height` btw. – George Mauer May 31 '16 at 19:32
  • @GeorgeMauer Ok, so when you said _I want both rows to size to their contents_, what does that actually mean? ... that the `section` should always fill the remaining space left by the `tabs`? – Asons May 31 '16 at 19:40
  • exactly. The section should fill space vertically (it has `flex-grow: 1`) and the tabs should collapse to their contents – George Mauer May 31 '16 at 19:44
  • 1
    @GeorgeMauer Updated with a 2:nd suggestion, using a wrapper, as the wanted behavior is not going to be achieved with the existing markup/css – Asons May 31 '16 at 20:21
  • I appreciate this and it helps a lot. Going to accept Michael's answer as it helps explain a bit better to me whats going on and taught me something about `flex-wrap` but thank you very much! – George Mauer May 31 '16 at 20:35
1

I only see a solution if the height of the tabs is known using calc to calculate the height of the section:

body {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  margin: 0;
  padding: 0;
  height: 100vh;
}

body > a {
  padding: 0 10px;
  margin: 0 5px;
  order: -1;
  flex: 0 1 auto;
  border: solid gray;
  border-width: 1px 1px 0 1px;
  line-height: 30px;
}

body > a:last-of-type {
  order: -2;
}

section {
  flex: 0 1 100%;
  background-color: cornsilk;
  display: none;
  border-top: 1px solid grey;
  height: calc(100% - 32px)
}

section:last-of-type {
  display: block;
}

a {
  font-weight: normal;
}

a:last-of-type {
  font-weight: bold;
}

:target {
  display: block;
}

:target ~ section {
  display: none;
}

:target ~ a {
  font-weight: normal;
}

:target + a {
  font-weight: bold;
}
<section id="advanced">
  Advanced Stuff
</section>
<a href="#advanced">Advanced</a>

<section id="home">
  Home Things
</section>
<a href="#home">Home</a>
  • If you're doing that, you don't need to `calc`. Remove the `align-content` rule and set all `body>a` to have `height: 32px`. Not a bad solution actually... – George Mauer Jun 01 '16 at 01:22