4
votes

I'm trying to create a horizontal navigation menu with flexible spacing between items. Flexbox seems like the appropriate choice, but I'm having trouble getting it working as I intend.

The requirements are:

  • If there is enough space available, have 4rems of space between each item
  • As the viewport width decreases, shrink the space between items evenly, down to a minimum of 2rem
  • If the viewport width decreases further, keep 2rem space between and shrink the items themselves

I would like this to accommodate a variable number of items (there'll be a maximum).

The closest I got was using 2rem padding on the items, and then making the items themselves flex containers, with shrinking pseudo-elements, but the shrinking is quite uneven.

HTML

<div class="container">
  <div class="item">
    Item 1
  </div>
  <div class="item">
    Item 2
  </div>
  <div class="item">
    Item 3 With Long Name
  </div>
  <div class="item">
    Item4WithLongNameNoSpaces
  </div>
  <div class="item">
    Item 5
  </div>
</div>

CSS

.container {
  display: flex;
}

.item + .item {
  display: flex;
  padding-left: 2rem;
}

.item + .item:before {
  content: "";
  display: block;
  width: 2rem;
  flex-shrink: 1000;
}

Codepen: https://codepen.io/qubaji/pen/PoZLEbW

It seems like flexbox could do it if I could find the right combination of flex-grow/flex-shrink values, but I can't quite get there. Any help much appreciated!

2
@MrT That was my original try but this has no upper bound on the space between items, whereas I'd like it to have a maximum EDIT: Oh you deleted your comment about justify-content: space-between but I'll leave my reply here in case others have the same question - piemanji
yeah, sorry, didn't think you will pick it up so quickly :) - Mr T
Thats a good question, i would probably try out justify-content: space-evenly or space-between and put empty divs with max-width: 2rem in between your items. Sometimes using empty divs as spacers is a very usable solution - Warden330
@Warden330 justify-content: space-evenly and space-between wouldn't match the requirements, but spacer divs are a good shout - piemanji

2 Answers

2
votes

I would consider display:contents to make your pseudo element at the same level of the flex items but this may limit the CSS you can apply to your items:

.container {
  display: flex;
  outline:1px solid red;
}

.item {
  display: contents;
}

.item + .item::before {
  content: "";
  width: 4rem;
  min-width:2rem;
  flex-shrink:9999;
  outline:1px solid green;
}
<div class="container">
  <div class="item">
    Item 1
  </div>
  <div class="item">
    Item 2
  </div>
  <div class="item">
    Item 3 With Long Name
  </div>
  <div class="item">
    Item4WithLong
  </div>
  <div class="item">
    Item 5
  </div>
</div>

Since you said that your elements will be limited to a maximum number, here is a CSS grid idea:

/* extra container to hide the overflow */
.wrapper {
  overflow:hidden; 
  outline:1px solid red;
}
/**/
.container {
  display: inline-grid;
  vertical-align:top;
  /* the item will take only the auto (even columns)
     the 1fr will define the shrinkable gap
  */
  grid-auto-columns:1fr auto; 
  justify-content:flex-start;
  grid-auto-flow:column; /* column flow */
  margin-left:-2rem; /* to hide the pseudo element */
}
/* this will define the size if the 1fr */
.container:before {
  content:"";
  width:2rem;
}
/* */

.item + .item {
  padding-left:2rem; /* the fixed gap */
}

/* you can easily generate the below using SASS/Less*/
.item:nth-child(1) {grid-column:2}
.item:nth-child(2) {grid-column:4}
.item:nth-child(3) {grid-column:6}
.item:nth-child(4) {grid-column:8}
.item:nth-child(5) {grid-column:10}
.item:nth-child(6) {grid-column:12}
.item:nth-child(7) {grid-column:14}
.item:nth-child(8) {grid-column:16}
.item:nth-child(9) {grid-column:18}
.item:nth-child(10) {grid-column:20}
<div class="wrapper">
  <div class="container">
    <div class="item">
      Item 1
    </div>
    <div class="item">
    Item 2
  </div>
    <div class="item">
      Item 3 With Long Name
    </div>
    <div class="item">
      Item4WithLong
    </div>
    <div class="item">
      Item 5
    </div>
  </div>
</div>
0
votes

A possible solution (or approximation) using spacer divs, as suggested by Warden330.

.container {
  display: flex;
  justify-content: center;
}
.item {
  white-space: nowrap;
}
.spacer {
  min-width: 2rem;
  max-width: 4rem;
  width: 100%;
  flex-shrink: 2;
}
<div class="container">
  <div class="item">
    Item 1
  </div>
  <div class="spacer"></div>
  <div class="item">
    Item 2
  </div>
  <div class="spacer"></div>
  <div class="item">
    Item 3 With Long Name
  </div>
  <div class="spacer"></div>
  <div class="item">
    Item4WithLongNameNoSpaces
  </div>
  <div class="spacer"></div>
  <div class="item">
    Item 5
  </div>
</div>