CSS Flex - how do I make div content scrollable? - html

I'm trying to create a sidebar which has 2 boxes. Each box can contain any number of list items(could be hundreds of items), but it's likely that the first will contain only a few items, and the second will contain many more.
I have the following (created in ReactJS):
<div class="drawer">
<div class="drawerContainer">
<div class="sideboxWrapper">
<SideBox header="Box 1" />
<SideBox header="Box 2" />
</div>
</div>
</div>
Each SideBox generates the following structure:
<div class="sidebox">
<h2 class="boxheader">{header}</h2>
<div class="boxContent">
<ul class="sideboxList">
<li class="listItemText listItemTextCurrent">Item 1</li>
<li class="listItemText listItemTextCurrent">Item 2</li>
... more items ...
</ul>
</div>
</div>
So, first of all, I want the boxes to fill the sidebar, but not overflow it. If the boxes are short enough to both fit, then they should both take up as much space as they need, and any remaining space is left blank. If they don't both fit, then they should take up the available space, and the boxContent div should become scrollable.
.drawer {
flex: 0 0 240px;
height: 100%;
overflow-y: hidden;
padding: 10px 0;
}
.drawerContainer {
height: inherit;
overflow: hidden;
padding-bottom: 10px;
padding-top: 10px;
}
.sideboxWrapper {
display: flex;
flex-direction: column;
height: inherit;
overflow: hidden;
}
.sidebox {
margin: 10px;
padding: 0px;
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
}
.boxContent {
overflow-y: auto;
}
The problem I'm having is that when one of the lists is very long, the sideboxes just expand to fit it, even if that means going outside the sideboxWrapper (the overflow is hidden, so the part of the sidebox outside the sideboxWrapper can't be read). What I want to happen is that the flex layout arranges the sideboxes to fit inside the sideboxWrapper, and then the boxheaders are shown and the boxContent is resized (with scrollbars) to fit within the remaining space. So, a long list will have scrollbars.
How can I achieve this?

here is what could be your CSS , I added border so you can see where stands some of your boxes. Using flexbox on so many levels requires to follow clearly what you are doing and see where flex is avalaible and needed.
* {
box-sizing: border-box;
}
.drawer {
width: 240px;/* without a flex parent that works too */
height: 100vh;/* example for a full screen height */
overflow-y: hidden;
padding: 10px 0;
border: solid;
}
.drawerContainer {
height: 100%;
padding-bottom: 10px;
padding-top: 10px;
}
.sideboxWrapper {
display: flex;
flex-direction: column;
height: 100%;
border: solid green;
overflow: hidden;
}
.sidebox {
margin: 10px;
padding: 0px;
flex-basis: 50%;/* make it half */
display: flex;
flex-direction: column;
min-height: 0;/* what is needed */
}
.boxContent {
flex-grow: 1;
overflow-y: auto;
}

Related

Flex-grow takes up its own margin as well when scrolling is needed

I have a container, in my case the body and html tags are the containers. And then I have 3 divs in them and I want the last one to fill the remaining vertical space available while still having a margin.
The third div is generated dynamically so I can't predict what height it's gonna need. The problem is, if it grows too much and a scrollbar is required, the bottom-margin it used to have goes away too. If a scrollbar is NOT required and doesn't appear, the margin is still there and everything looks like I want it to.
I tried to draw what I meant as best as I could in the above image. The 1st case is what I want to happen all the time, regardless of whether there's a scrollbar or not. The 2nd picture is what actually happens, the blue div loses its bottom margin, despite having it set.
Here's my CSS for the html and body tags (they contain the 3 divs, including the blue one):
html,
body
{
width: 100%;
height:100%;
margin: 0px;
padding: 0px;
display: flex;
flex-direction:column;
align-items: center;
background: #494d5f ;
}
and here's my code for the 3rd div, the blue one:
.bottomDiv
{
display:flex;
flex-direction:column;
background: #a0d2eb ;
align-items: center;
width: 97%;
margin-bottom:1.5%;
flex: 1 1 auto;
padding-top:1%;
padding-bottom:1%;
}
Maybe I didn't clarify well enough but the 3rd div in my case, the blue one grows just like it should, fully obeying its margins UNTIL a scrollbar appears and is needed. No matter the amount of growth it has to do, it does it perfectly while respecting its margin. But if it has to grow "out of bounds" of the page so to say, as in, a scrollbar is needed to display all the webpage then its margin is simply gone. IF there is NO scrollbar, everything looks perfect.
Just change this line: body { height: 100%; } to: body { min-height: 100vh; }.
With that line the body will have a height of at least the screen height (100vh) but allows i proper overflow as it is allowed to eb alrger then the screen. As such the margins wont get removed.
html,
body {
width: 100%;
min-height: 100vh;
margin: 0px;
padding: 0px;
display: flex;
flex-direction: column;
align-items: center;
background: #494d5f;
}
.topDiv,
.midDiv {
width: 97%;
height: 50px;
}
.topDiv {
margin-top: 1.5%;
background-color: red;
}
.midDiv {
background-color: blue;
}
.bottomDiv {
display: flex;
flex-direction: column;
background: #a0d2eb;
align-items: center;
width: 97%;
margin-bottom: 1.5%;
flex: 1 1 auto;
padding-top: 1%;
padding-bottom: 1%;
}
#height:checked + label::after {
content: "";
display: block;
height: 150vh;
}
<div class="topDiv"></div>
<div class="midDiv"></div>
<div class="bottomDiv">
<input type="checkbox" id="height" name="height">
<label for="height">checkmark me to extend box height</label>
</div>
I've put an example, so you may try this approach:
body,
html {
margin: 0;
padding: 0;
}
#page {
display: flex;
flex-direction: column;
height: 100vh;
outline: 1px solid purple;
}
#header {
height: 30px;
outline: 1px solid red;
}
#middle {
flex-grow: 1;
/* height: 100vh; */
/* flex-direction: column; */
outline: 1px solid green;
}
#footer {
outline: 1px solid blue;
}
<div id="page">
<div id="header">...</div>
<div id="middle">...</div>
<div id="footer">...</div>
</div>
Try to remove #footer and see that #middle fills the entire screen till the end.
In addition if you need any margins so you have to modify the container like this: #page { ..., margin: 10px; height: calc(100vh - 20px); }

How to reduce, using CSS, a div's width once its max-height has been reached?

I'm trying to create a menu that can contain an undefined amount of objects; said objects' sizes are dependent on the available width (the width of the menu). The menu's height should never exceed certain value and still contain all the child objects, meaning that the child elements can shrink, while maintaining proportions, but never overflow the "containment area".
Something like this:
add = () => {
const menuObject = document.createElement('div')
menuObject.classList.add('menu-element')
const menu = document.getElementById('menu')
menu.appendChild(menuObject)
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
button{
padding: 40px;
}
.bounds{
position: absolute;
border: 2px dashed black;
width: 10%;
right: 8px;
bottom: 8px;
height: 60%;
box-sizing: content-box;
}
.menu{
position: absolute;
width: 10%;
right: 10px;
bottom: 10px;
background-color: chocolate;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.menu-element{
width: 100%;
background-color: dodgerblue;
margin: 5%;
}
.menu-element::before{
content: "";
padding-top: 100%;
display: block;
}
<body>
<button onclick="add()">Add item</button>
<div class="bounds">
Items should never leave this box
</div>
<div id="menu" class="menu"></div>
</body>
I have tried setting a max-height attribute to the menu, but that doesn't modify its width which is, ultimately, the value that controls the whole sizing schema. I'm looking for a CSS only solution, if it's at all possible, and any light that can be shed on the issue will be greatly appreciated.
If you want the menu to be contained by some bounds, it should be placed within these bounds so it can expand into them.
As the menu is display: flex, we can have child elements share the available space within the menu by adding flex: 1 to them.
We can optionally add a max-height value to the menu items if we want to.
add = () => {
const menuObject = document.createElement('div')
menuObject.classList.add('menu-element')
const menu = document.getElementById('menu')
menu.appendChild(menuObject)
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
button {
padding: 40px;
}
.bounds {
position: absolute;
border: 2px dashed black;
width: 10%;
right: 8px;
bottom: 8px;
height: 60%;
box-sizing: content-box;
}
.menu {
width: 100%; /* Expand into boundaries */
height: 100%; /* Expand into boundaries */
background-color: chocolate;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.menu-element {
flex: 1; /* Makes elements expand into available space */
max-height: 25%; /* Height cap in case we want to have a set initial size */
width: 100%;
background-color: dodgerblue;
outline: 1px solid red; /* Help visualize boundaries */
}
<button onclick="add()">Add item</button>
<div class="bounds">
<div id="menu" class="menu"></div> <!-- Move menu into boundaries -->
</div>
Set the container div's overflow to overflow: auto
.bounds { overflow: auto; } // for example
This will give scroll bars should the items inside exit the container borders.
Also, the flex-shrink property specifies how the item will shrink relative to the rest of the flexible items inside the same container. If the element is not a flexible item, the flex-shrink property has no effect.
Set element flexible with display: flex;
flex-shrink: number|initial|inherit; // Syntax
for example if you had flexible paragraphs:
p:nth-of-type(2){flex-shrink: 3;} //Second flex-item shrinks 3x more than the rest
number = A number specifying how much the item will shrink relative to the rest of the flexible items. Default value is 1.
initial = Sets this property to its default value.
inherit = Inherits this property from its parent element.
https://www.w3schools.com/CSSref/css3_pr_flex-shrink.asp flex-shrink guide
https://www.w3schools.com/CSSref/pr_pos_overflow.asp overflow guide

Display:flex and scrolling inner divs

https://jsfiddle.net/wqmm0kxb/5/
html:
<div class="full">
<header><h1>header stuff</h1></header>
<section>
<div>
{lots and lots of content}
</div>
<div>b</div>
<div>c</div>
</section>
</div>
css:
.full {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
header {
flex: 78px 0 0;
background: #ececec;
color: black;
padding-left: 33px;
}
section {
flex: auto 1 1;
display: flex;
align-items: stretch;
> div {
flex: auto 1 1;
overflow-y: auto;
}
}
}
My outer container, '.full', takes up the full width and height of the screen, and uses display:flex to make sure that the header + section children stretch to take up all the space beneath them.
Now, what I want is naturally for the header to take up 78px and the section to take up {full height - 78px} -- but without doing anything like calc preferrably. And I want to be able to scroll in the div children of section, without scrolling affecting the other divs or the page as a whole.
This works perfectly in Chrome, but open up my fiddle in firefox, edge, ie and it doesn't work as expected. Section gets the height of {lots and lots of content} rather than only taking the remaining space of '.full'
What should I do to achieve the Chrome-like layout that I'm expecting?
Apply the overflow-y:auto for your section also, that will fix the issue in IE and Firefox.
section {
flex: auto 1 1;
display: flex;
align-items: stretch;
overflow-y: auto;
> div {
flex: auto 1 1;
overflow-y: auto;
}
}
Fiddle DEMO

Why does this flex item not change according to its container's height?

What I'd Like to Happen
I have a .box whose height is constrained to stay within the viewport. Nested a couple levels within this element I have a .content-body that contains a header and a .content item. I'd like for just .content to shrink (with scrollbars displayed) when the height of .box is small enough.
What Happens
In Chrome it all works as intended (like the pic above), but in almost every other browser, it gets cutoff because the content doesn't want to shrink.
The Code
I made a CodePen to demonstrate the problem. Simply resize the height of the browser and observe how the content element responds. Here's the same code on StackOverflow for posterity.
/* the most relevant rules to the problem */
.container{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
.box {
display: flex;
flex-direction: column;
max-height: calc( 100vh - 50px );
position: relative;
overflow: hidden;
}
.box-body {
display: flex;
flex-flow: column;
}
.content-body{
display: flex;
flex-direction: column;
}
.content{
overflow: auto;
}
/* other stuff (appearance mostly) that probably doesn't matter */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.box{
/* appearance */
width: 600px;
border-radius: 20px;
border: 1px solid #bbb;
}
.box-body{
/* appearance */
padding: 20px;
font-family: sans-serif;
font-size: 18px;
}
.content-header{
/* appearance */
background: #ddd;
padding: 10px;
margin-bottom: 10px;
}
.box-label {
/* appearance */
padding: 30px 10px;
background: teal;
color: white;
font-family: sans-serif;
font-size: 20px;
}
.content{
padding: 10px;
}
p {
margin: 10px 0px;
}
<div class="container">
<div class="box">
<div class="box-label">
Header
</div>
<div class="box-body">
<div class="content-body">
<div class="content-header">
Content Header
</div>
<div class="content">
<p>Mr do raising article general norland my hastily. Its companions say uncommonly pianoforte favourable.</p>
<p>On projection apartments unsatiable so if he entreaties appearance. Rose you wife how set lady half wish. Hard sing an in true felt. Welcomed stronger if steepest ecstatic an suitable finished of oh. Entered at excited at forming between so
produce.</p>
<p>Answer misery adieus add wooded how nay men before though. Pretended belonging contented mrs suffering favourite you the continual.</p>
</div>
</div>
</div>
<div class="box-label">
Footer
</div>
</div>
</div>
An initial setting on flex items is min-height: auto.
This means that a flex item, in a column-direction container (like the ones in your code), cannot be shorter than its content.
As you have observed, flex items in Chrome do what you want right out of the box.
Firefox, Edge and possibly other browsers need you to override the default setting.
Add this to your code:
.box-body {
min-height: 0; /* new */
}
.content-body {
min-height: 0; /* new */
}
revised fiddle
Full explanation: Why doesn't flex item shrink past content size?

Height is not correct in flexbox items in Chrome [duplicate]

This question already has answers here:
Chrome / Safari not filling 100% height of flex parent
(5 answers)
Closed 6 years ago.
I've got a delicate problem for any CSS guru out there.
My green div has a flexible height, taking up the remaining.
And now I want to put a div inside that div which should be the half of the green div. But it seems like if Chrome treats it like half of the whole page rather than the flex item.
http://jsfiddle.net/unh5rw9t/1/
HTML
<body>
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
</body>
CSS
html,body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
background-color: green;
}
#half_of_content {
height: 50%;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
#Michael_B explained why Chrome behaves like this:
You gave the body a height: 100%. Then gave its child (.wrapper)
a height: 100%. Then gave its child (.content) a height: 100%.
So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be a 50% height
of body.
However, Firefox disagrees because, in fact, that height: 100% of .content is ignored and its height is calculated according to flex: 1.
That is, Chrome resolves the percentage with respect to the value of parent's height property. Firefox does it with respect to the resolved flexible height of the parent.
The right behavior is the Firefox's one. According to Definite and Indefinite Sizes,
If a percentage is going to be resolved against a flex item’s
main size, and the flex item has a definite flex
basis, and the flex container has a definite main
size, the flex item’s main size must be treated as
definite for the purpose of resolving the percentage, and the
percentage must resolve against the flexed main size of the
flex item (that is, after the layout algorithm below has been
completed for the flex item’s flex container, and the flex
item has acquired its final size).
Here is a workaround for Chrome:
#content {
display: flex;
flex-direction: column;
}
#content::after {
content: '';
flex: 1;
}
#half_of_content {
flex: 1;
height: auto;
}
This way the available space in #content will be distributed equally among #half_of_content and the ::after pseudo-element.
Assuming #content doesn't have other content, #half_of_content will be 50%. In your example you have a 2 in there, so it will be a bit less that 50%.
html,
body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
background-color: green;
display: flex;
flex-direction: column;
}
#content::after {
content: '';
flex: 1;
}
#half_of_content {
flex: 1;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
You could absolutely position div id="half_of_content".
#content {
flex: 1;
height: 100%;
background-color: green;
position: relative; /* new */
}
#half_of_content {
height: 50%;
background-color: yellow;
position: absolute; /* new */
width: 100%; /* new */
}
DEMO
With regard to your statement:
But it seems like if Chrome treats it like half of the whole page
rather than the flex item.
You gave the body a height: 100%. Then gave its child (.wrapper) a height: 100%. Then gave its child (.content) a height: 100%. So they're all equal height. Giving the next child (#half_of_content) a height: 50% would naturally be 50% height of body.
With absolute positioning, however, you don't need to specify parent heights.
Nesting flexboxes is a little buggy. I reworked your markup a little by adding an inner wrapper with display: flex; which seems to do the job. Here is the fiddle (also using class names instead of ids).
<div class="content">
<div class="wrapper-inner">
2
<div class="half">
2.1
</div>
</div>
</div>
.wrapper-inner {
position: absolute;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
Fix:
on #content set
display: flex;
flex-flow: column nowrap;
justify-content: flex-end
on #half_of_content set flex: 0 0 50%;
Caveat: you need to add an extra div as a child of #content.
Here's the full example:
html,body {
height: 100%;
margin: 0;
}
#wrapper {
display: flex;
flex-flow: column;
height: 100%;
}
#menu {
height: 70px;
background-color: purple
}
#content {
flex: 1;
height: 100%;
display:flex;
flex-flow: column nowrap;
justify-content: flex-end;
background-color: green;
}
#half_of_content {
flex: 0 0 50%;
background-color: yellow;
}
#footer {
height: 100px;
background-color: cyan
}
<body>
<div id="wrapper">
<div id="menu">
1
</div>
<div id="content">2
<div id="half_of_content">2.1</div>
</div>
<div id="footer" style="">
3
</div>
</div>
</body>