white-space: nowrap breaks flexbox layout

PieBie picture PieBie · Jul 6, 2016 · Viewed 23.2k times · Source

I have created a responsive layout for an app using Flexbox. The layout calls for a collapsible menu on the left, a block with a header and body in the middle and a toggleable help-pane on the right (there's more to it but that's the basic structure).

The left menu has two states: 180px wide or 80 px wide. The help pane is either hidden, or takes 180px. The middle box takes the rest of the space. Flexbox works like a charm.

The trouble starts when I make a scrolling div using white-space: nowrap. I have a bunch of items that need to be displayed in a horizontal scroller, so I have a list div with the items, set to overflow:auto and white-space: nowrap.

Usually this works like a charm, but now it breaks my flex layout. Instead of taking the width of the parent (flex) div, the scroller makes the div wider, which in turn pushes the help-pane out of bounds.


The following fiddle illustrates this issue:

http://jsfiddle.net/PieBie/6y291fud/

You can toggle the help-pane by clicking toggle help in the menu bar. Recreate the issue by clicking list whitespace toggle in the menu, this toggles the white-space: no-wrap CSS property of the list. If the help-pane is open, you can see it gets pushed out of bounds.

The bottom list is what I want to achieve, but I want it to be full width of its parent.

I can recreate the issue in Chrome, Firefox, Opera, Vivaldi and Edge. Internet Explorer 11 plays nice (°_°). I would prefer a pure CSS solution (SCSS is also an option), but if need be I can use JS.


$('#nav-toggle').on('click',function(){
	$(this).parent().toggleClass('collapsed');
});
$('#help-toggle').on('click',function(){
	$('#help-pane').toggleClass('visible');
});
$('#list-toggle').on('click',function(){
	$('#list').toggleClass('nowrap');
});
body,html{width:100%;height:100%;overflow:hidden;}

#body{
  display:flex;
  flex-flow:row nowrap;
  position:absolute;
  top:0;
  left:0;
  margin:0;
  padding:0;
  width:100%;
  height:100%;
  background-color:#abc;
  overflow:hidden;
}

#shell{
  flex: 1 1 auto;
  display:flex;
  flex-flow:row nowrap;
  position:relative;
  width:100%;
  min-height:100%;
}

  #left{
    flex: 0 0 180px;
    min-height:100%;
    min-width: 0;
    background:lightblue;
  }
  #left.collapsed{
    flex: 0 0 80px;
  }
  
  #mid{
    flex: 1 1 auto;
    min-height:100%;
    min-width: 0;
    display:flex;
    flex-flow:column nowrap;
    align-items:stretch;
    align-content:stretch;
    position:relative;
    width:100%;
    min-height:100vh;
    min-width: 0;
    background:purple;
  }
      #mid-top{
        flex: 0 0 auto;
        min-height:100px;
        background:green;
      }
      #mid-bottom{
        min-height:calc(100% - 100px);
        flex: 1 1 auto;
        background:lightgreen;
      }
      #list{
        overflow: auto;
        width: 100%;
        max-width: 100%;
      }
      #list.nowrap{
        white-space: nowrap;
      }
      #secondlist{
        overflow: auto;
        max-width: 250px;
        white-space: nowrap;
      }
      .list-item{
        display: inline-block;
        width: 50px;
        height: 50px;
        margin: 2px;
        background: purple;
      }
      .list-item.odd{
        background: violet;
      }
      
#help-pane{
  display:none;
  flex: 0 0 0px;
  background:red;
}
#help-pane.visible{
  display:inherit;
  flex:0 0 180px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="body">
 <div id="shell">
      <div id="left">
          <div id="nav">
            - menu -
          </div>
          <div id="help-toggle">
            help toggle
          </div>
          <div id="nav-toggle">
            nav toggle
          </div>
          <div id="list-toggle">
            list whitespace toggle
          </div>
      </div>
      <div id="mid">
          <div id="mid-top">
                - mid top -
          </div>
          <div id="mid-bottom">
               - mid bottom- <br><br>
               <div id="list">
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
               </div>
               <hr>
               <div id="secondlist">
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
               </div>
          </div>
      </div>
 </div>
 <div id="help-pane" class="visible">
   - help-pane -
 </div>
</div>

Answer

PieBie picture PieBie · Jul 6, 2016

This is caused by the default flex-box behaviour, which prevents flex-boxes of becoming smaller than it's contents.

The solution to this issue is setting min-width: 0 (or min-height: 0 for columns) to all parent flex-boxes. In this specific case (and in the fiddle):

#shell{
  flex: 1 1 auto;
  display:flex;
  flex-flow:row nowrap;
  position:relative;
  width:100%;
  min-height:100%;
  min-width: 0; /* this one right here does it!*/
} 

$('#nav-toggle').on('click',function(){
	$(this).parent().toggleClass('collapsed');
});
$('#help-toggle').on('click',function(){
	$('#help-pane').toggleClass('visible');
});
$('#list-toggle').on('click',function(){
	$('#list').toggleClass('nowrap');
});
body,html{width:100%;height:100%;overflow:hidden;}

#body{
  display:flex;
  flex-flow:row nowrap;
  position:absolute;
  top:0;
  left:0;
  margin:0;
  padding:0;
  width:100%;
  height:100%;
  background-color:#abc;
  overflow:hidden;
}

#shell{
  flex: 1 1 auto;
  display:flex;
  flex-flow:row nowrap;
  position:relative;
  width:100%;
  min-height:100%;
  min-width: 0;
}

  #left{
    flex: 0 0 180px;
    min-height:100%;
    min-width: 0;
    background:lightblue;
  }
  #left.collapsed{
    flex: 0 0 80px;
  }
  
  #mid{
    flex: 1 1 auto;
    min-height:100%;
    min-width: 0;
    display:flex;
    flex-flow:column nowrap;
    align-items:stretch;
    align-content:stretch;
    position:relative;
    width:100%;
    min-height:100vh;
    min-width: 0;
    background:purple;
  }
      #mid-top{
        flex: 0 0 auto;
        min-height:100px;
        background:green;
      }
      #mid-bottom{
        min-height:calc(100% - 100px);
        flex: 1 1 auto;
        background:lightgreen;
      }
      #list{
        overflow: auto;
        width: 100%;
        max-width: 100%;
      }
      #list.nowrap{
        white-space: nowrap;
      }
      #secondlist{
        overflow: auto;
        max-width: 250px;
        white-space: nowrap;
      }
      .list-item{
        display: inline-block;
        width: 50px;
        height: 50px;
        margin: 2px;
        background: purple;
      }
      .list-item.odd{
        background: violet;
      }
      
#help-pane{
  display:none;
  flex: 0 0 0px;
  background:red;
}
#help-pane.visible{
  display:inherit;
  flex:0 0 180px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="body">
 <div id="shell">
      <div id="left">
          <div id="nav">
            - menu -
          </div>
          <div id="help-toggle">
            help toggle
          </div>
          <div id="nav-toggle">
            nav toggle
          </div>
          <div id="list-toggle">
            list whitespace toggle
          </div>
      </div>
      <div id="mid">
          <div id="mid-top">
                - mid top -
          </div>
          <div id="mid-bottom">
               - mid bottom- <br><br>
               <div id="list">
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
               </div>
               <hr>
               <div id="secondlist">
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
                 <div class="list-item">&nbsp;</div>
                 <div class="list-item odd">&nbsp;</div>
               </div>
          </div>
      </div>
 </div>
 <div id="help-pane" class="visible">
   - help-pane -
 </div>
</div>