Scrolling inside nested flexboxes
I am working on a React app that has modal content with some tabs that sometimes require scrolling inside. However, the problem is that I cannot get the scrolling of the relevant content.
The modal is split into 3 main sections:
- Heading
- Should always be visible at the top of the modal
- Content
- Should fill the gap between the header and footer , but never force it to appear
- Footer
- Must always be visible under Content
- This could mean at the bottom of the modal (if the Content is filling the remaining space) or below the Content (if the Content is t filling the space)
While it would be easy to implement (and I've already done it), the problem arises because the Content shouldn't scroll, but rather something inside it. The images below show two examples of intended behavior, one with long content that needs to scroll, and one without.
I am using a custom tab that does something inside a container. This container (white in the images below) is what needs to be scrolled if needed.
The farthest I have come can be found in the following CodePen code and example. Currently, I can scroll to an element containing the intended scrollable content, but I cannot actually scroll that content. If you check Pen, you can see that it (for some reason) passes by the containing element.
Sample code
Html
<div class="modal">
<div class="background" />
<div class="modal_content">
<div class="header">Random header</div>
<div class="tabbed_content">
<div class="tabs">
<div class="tab active">Tab 1</div>
<div class="tab">Tab 2</div>
</div>
<div class="content">
<div class="scrollable">
Tabbed Content
<br /><br /><br /><br /><br /><br />
Another Couple Lines
<br /><br /><br /><br /><br /><br />
More Tabbed Content
<br /><br /><br /><br /><br /><br />
Even More Content!
<br /><br /><br /><br /><br /><br />
Why not more yet!
<br /><br /><br /><br /><br /><br />
Some Ending Content
<br /><br /><br /><br /><br /><br />
Final Content!
</div>
</div>
</div>
<div class='footer'>
<button class='button'>
Done
</button>
</div>
</div>
</div>
CSS
body {
font-family: 'Montserrat', sans-serif;
}
/* Modal wrapper */
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
/* Modal background styles */
.background {
position: absolute;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.5)
}
/* Modal content */
.modal_content {
height: 100vh;
display: flex;
flex-direction: column;
width: 85%;
margin: 0 auto;
background-color: white;
}
.header {
margin-bottom: 1rem;
padding: 1rem;
font-size: 125%;
text-align: center;
font-weight: bold;
background-color: #EEEEEE;
border-bottom: 1px solid #CECECE;
}
/* Contains the tabs and content */
.tabbed_content {
display: flex;
flex-direction: column;
align-items: stretch;
}
/* IGNORE */
.tabs {
display: flex;
/* NOTE: Added to stop them from hiding */
flex-shrink: 0;
}
/* IGNORE */
.tab {
flex-grow: 1;
margin-top: 8px;
padding: 0.5rem;
text-align: center;
background-color: #CECECE;
}
/* IGNORE */
.tab.active {
margin-top: 0;
font-weight: bold;
background-color: #EEEEEE;
}
/* Contains the current tab content */
/* NOTE: This shouldn't scroll */
.content {
padding: 1rem;
background-color: #EEEEEE;
/* NOTE: Currently the closest I can come */
/*overflow: auto;*/
}
/* IMPORTANT: This should scroll if necessary! */
.scrollable {
padding: 0.5rem;
background-color: white;
/* NOTE: Can't get this to scroll */
/*overflow: auto;*/
border: 1px dashed #CECECE;
}
.footer {
display: flex;
flex-align: center;
flex-shrink: 0;
justify-content: center;
margin-top: 1rem;
padding: 1rem;
border-top: 1px dashed #CECECE;
}
/* IGNORE */
.button {
padding: 0.75rem 1rem;
font-size: 90%;
color: white;
background-color: lightseagreen;
border: none;
border-radius: 2px;
cursor: pointer;
}
I'm almost at it with this one (spent 2 hours showing). Thanks in advance for your help!
PS I also need to worry about stopping the background scrolling while the modal is open at some point ...
source to share
Generally speaking, in order overflow: auto
to create a vertical scrollbar, you need to define the height on the container.
For the effect to
overflow
have an effect, a block-level container must have either a given height (height
, ormax-height
) orwhite-space
set tonowrap
.source: https://developer.mozilla.org/en-US/docs/Web/CSS/overflow
However, this is not always a practical solution, especially in dynamic environments.
In your layout, a simple solution is to make container ( .content
) a flex container. This is enough to make the mock work in Chrome ( demo ).
However, in order for the layout to work in Firefox and Edge as well, you need to override the default minimum height of flex items, which is min-height: auto
. This prevents flex items from shrinking below the size of their content, which prevents them from occurring overflow
. ( full explanation )
Make these changes to your code:
.tabbed_content {
display: flex;
flex-direction: column;
align-items: stretch;
min-height: 0; /* NEW, esp for FF & Edge (see link below) */
}
.content {
padding: 1rem;
background-color: #EEEEEE;
overflow: auto; /* KEEP THIS, for FF & Edge (see link below) */
display: flex; /* NEW */
flex-direction: column; /* NEW */
}
.scrollable {
padding: 0.5rem;
background-color: white;
overflow: auto; /* RESTORE */
border: 1px dashed #CECECE;
}
Read more: Why won't the element bend as the content is reduced?
source to share
Layout, how difficult it is! I usually try to create simplified versions and work.
One way you could do this is by positioning absolute for the scrollable div.
https://codepen.io/sheriffderek/pen/zwQxLr/ - see this pen for long and short examples
<aside class="window">
<header>header</header>
<main>
<div class="scroll-area">
<ul>
<li>short conent</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
</div>
</main>
<footer>footer</footer>
</aside>
CSS (this is the stylus - but this is just syntax - and the concept should be clear)
.window
border: 10px solid $gray;
max-width: 600px // arbitrary
height: 80vh // arbitrary
display: flex
flex-direction: column
header, main, footer
padding: $pad
header
background: $color
main
flex-grow: 1
background: $alternate
position: relative
.scroll-area
position: absolute
top: 0
left: 0
width: 100%
height: 100%
overflow: auto
padding: $pad
footer
background: $highlight
For your additional question, you can add / remove a class from the body when you open the modal and stop scrolling.
source to share