Scrolling element inside a fill-height element - html

I need to create a web layout with a header and a main area. The header's height will grow with its content and the main area will fill the remaining vertical space. Cool, this is easily done either with flex or grid. BUT! Inside the main area I need another element (let's call him badboy) which can have a lot of content and I need it to scroll, not to stretch my main area beyond the lower border of my page.
For the main area I have tried flex with flex-grow: 1 or grid row with size auto. But since such elements do not have a specific height, the badboy always stretches the main element so no overflow happens anywhere.
My HTML:
<div class="app">
<header></header>
<main>
<div class="badboy"></div>
</main>
</div>
The Layout:
If only I could fix the height of the header to a specific height, I could use calc to set the main area's height exactly and problem solved. But my header needs to grow with content. There already is a similar question here saying its impossible, but it is 5 years old and CSS has come long way since then.

Ok, so by digging around some more I have come up with this solution with CSS grid. I don't know if this is hacky or just regular stuff, but it seems to work.
.app {
width: 100vw;
height: 100vh;
grid-template-columns: 100%;
grid-template-rows: fit-content(1px) minmax(1px, auto);
display: grid;
}
header {
background: lightgray;
}
main {
background: gray;
}
.badboy {
max-width: 40rem;
margin: 0 auto;
max-height: 100%;
overflow-y: auto;
background-color: white;
}

Related

Setting flex-grow: 1 causes div height to go beyond available space

I have some html like this:
.container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
padding: 10px;
background-color: aqua;
}
.box {
width: 100%;
height: 100%;
flex-grow: 1;
background-color: cadetblue;
display: flex;
flex-direction: column;
}
.fill {
flex-grow: 1;
background-color: antiquewhite;
}
.middle {
height: fit-content;
background-color: azure;
justify-self: flex-end;
}
.bottom {
height: fit-content;
background-color: darkgray;
justify-self: flex-end;
}
<div class="container">
<h3>Title</h3>
<div>Some Stuff</div>
<div class="box">
<div class="fill">Fill</div>
<div class="middle">Middle</div>
<div class="bottom">Bottom</div>
</div>
</div>
I want the divs middle and bottom to stack at the bottom of the screen, and for the div fill to, as the name suggests, fill the remaining space without pushing the middle/bottom divs off the screen or creating a scrollbar, however it doesn't display like that:
Note that the middle and bottom divs are not visible and the scrollbar created by the fill div expanding beyond the available height.
See this StackBlitz for a demo: https://stackblitz.com/edit/angular-ivy-mwtwdg?file=src/app/hello.component.scss
I had to update a lot of CSS but feel free to take a look.
The right approach to any angular project is to have clean from the very first component (app component), and cascade it down to any other component.
Demo stackblitz
EDIT : comment explanation
"The right approach" can be explained quickly like this :
html and body should be at 100% of the page
App component should be at 100% and in display block
Any component that requires some specific layout (flex, grid), should be constrained by its parent or an absolute size, and display block.
The issue with Angular is that when you create a component, it is not set to display: block. This means the component is kind of free in the DOM flow, and this results in the kind of issues you have encountered.
When you set display: block to EVERY component (you can use angular.json to make it automatic), then you have a more deterministic approach to the CSS, where what you expect is what you get.
In your case, since the components were not display:block, they could not be constrained by height, width, or their parents.
Added to that, the fact that you wrote some probelmatic CSS (for instance, the sidenav-container being 100% of the height of its parent : what about the toolbar ?), this resulted in your issue.
As a final word, when it comes to CSS in Angular, be sure to have clean CSS from the top, and when you have any issue like you did, crawl back component by component, to find and correct the unclean ones !

Overflow works on outer 100% height div but not inner 100% height div

I have a CSS grid with a row div for a header, a row div for content, and a row div for a footer. I want the content to fill the screen but keep the header and footer visible. I have a fixed-size div inside another div inside the content div. Everything except the fixed-size div is height: 100%.
If I apply overflow-y: auto to the content div, the scrollbar appears on the content div. This is great, but what I really want is for the scrollbar to appear on the div inside the content div instead.
https://jsfiddle.net/efth2akr/2/
If I apply overflow-y: auto to the div inside the content div instead, there is no scrollbar and the content div takes on the height of the fixed-size div. This pushes the footer down and puts a scrollbar on the whole page. What?? Isn't height: 100% supposed to be based on the parent height? Other questions that describe similar scenarios fail to put height: 100% all the way up the chain, but I haven't.
https://jsfiddle.net/t08u9wnk/2/
How can I achieve my desired behavior of having the scrollbar appear on the div inside the content div while maintaining a responsive layout? What am I not understanding about height: 100% in this scenario?
Browser: Microsoft Edge 103.0.1264.62
I think the approach you are trying to follow would work only if you set a fixed height or max-height to div.grid-content > div. Now the problem is that the height of this container is dynamic and depends on height: 100%, so you might be tempted to do max-height: 100% but here is a good reason for why this is not possible.
If you want div.grid-content > div to have a scroll and keep the layout of the entire page based on the height of the screen, I'd propose you to use an absolute positioned overlay.
Here is a modified snippet of your code explaining how it works:
html, body {
height: 100%;
width: 100%;
}
body {
/* prevents scroll on the entire page */
overflow: hidden;
}
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
div.grid {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 1fr;
height: 100%;
width: 100%;
}
div.grid-header, div.grid-footer {
background-color: #ff0000;
}
div.grid-content {
background-color: #00ff00;
/* set relative positioning to contain absolute child */
position: relative;
}
div.grid-content > div {
/* this is strategy for creating a container that adapts to the size of its relative parent using absolute positioning without the need of setting a fixed height to div.grid-content */
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* add scroll on y axis */
overflow-y: auto;
}
div.grid-content > div > div {
height: 200vh;
width: 50%;
background-color: #0000ff;
}
<div class="grid">
<div class="grid-header">
<h3>
hi
</h3>
</div>
<div class="grid-content">
<div>
<div>
asdf
</div>
</div>
</div>
<div class="grid-footer">
<h3>
bye
</h3>
</div>
</div>
I have been bitten by this unintuitive design choice of CSS grid: https://github.com/w3c/csswg-drafts/issues/1777
In summary, CSS grid "1fr" behaves like minmax(auto, 1fr) (where 1fr is the actual fractional dimension of the grid). The minimum width/height of a "1fr" grid row/column is the auto width/height. If the auto width/height is larger than 1fr, the whole row/column will expand out past 1fr because minmax ignores the max when min > max!
This is why my problem has the same symptoms as all the other questions about height: 100% not working where the answer was that they had an ancestor with height: auto. It turns out 1fr can become auto!
Replacing:
grid-template-rows: auto 1fr auto;
with:
grid-template-rows: auto minmax(0, 1fr) auto;
fixes the problem.
https://jsfiddle.net/t08u9wnk/3/

CSS grid row overflows its container vertically

I would like to have a grid layout on a page where the grid stretches out to the entire viewport, and the rows have a minimum height. The simplest example would be a grid with a single cell (see code snippet below).
The problem I am having is that when the height of the viewport is less than the defined minimum row-height, the row vertically overflows its container. With the added red and green borders in the below example it's visible that the row's height isn't going below the defined 500 pixels, but the grid-container is still sized to the viewport which is now shorter than 500 pixels.
If I remove the height CSS attribute from the grid class, the container doesn't shrink below its content, but it also doesn't fill out the vertical space when the viewport is taller than 500 pixels. Since I want the grid to fill the entire page, I need the height CSS attribute. I've also added the min-height: fit-content attribute which is supposed to prevent the used value of the height property from becoming smaller than the value specified for min-height but it doesn't work (not with the defined fit-content value - it works as expected with an exact value, for example 300px).
In a similar question the culprit was the percentage values used for the gaps, but in this case there is nothing relatively sized. Even if replace the grid-template-rows: minmax(500px, 1fr); property with the fixed grid-template-rows: 500px;, it still behaves the same way.
body {
margin: 0;
}
.grid {
display: grid;
grid-template-rows: minmax(500px, 1fr);
height: 100vh;
min-height: fit-content;
width: 100vw;
}
.bordered {
border: 10px solid green;
}
<div class="grid bordered" style="border-color: red;">
<div class="bordered">Some content</div>
</div>
What I would like to have is a grid that fills out the entire viewport and where the grid-container is never smaller than its content. What am I missing?
Something to know is that as soon as a min height of a row, or the combined height of multiple rows, is greater than the height of the viewport, you will have a scroll. Beyond that, the snippet below, I hope does what you are looking for. I added comments in the code.
/* lines I added */
*{
box-sizing: border-box;
}
body {
margin: 0;
}
.grid {
display: grid;
/* 100 is for the small viewport here in the code snippet */
grid-template-rows: repeat(auto-fit, minmax(100px, 1fr));
min-height : 100vh;
}
.bordered {
border: 10px solid green;
}
<div class="grid bordered" style="border-color: red;">
<div class="bordered">Some content</div>
</div>

No - Scroll Page with extendable middle - part

Let's say you have a page which you don't want to scroll, constisting of three parts; a header, a main part and a footer:
<body>
<div id="page-content">
<header>My Header</header>
<main>Main Content</main>
<div class="footer-pusher"></div>
</div>
<footer>My Header</footer>
</body>
The page shall always be 100% high, the header and the footer have a fix height and 100% width, and the middle part shall stretch to fill the rest of the page, having a min-width in case of simply to small screens in terms of height. Only in that case, the page shall be scrollable. To achieve this, I used the following CSS, including the one to have a sticky footer:
body, #page-content {
height: 100%;
display: flex;
flex-direction: column;
}
.footer-pusher {
height: 200px;
margin-bottom: -200px;
}
footer {
height: 200px;
}
header {
height: 125px;
}
main {
flex-grow: 1;
min-height: 210px;
}
My question now is, how can I code via CSS that the main part expands to the remaining height of the page, and is at least 210px high? For illustrative purposes, I wrote the flex-grow property, as that's precisely the one I need, but in vertical and not in horizontal direction of the flexbox container. With the code above, the middle part is always 210px high..

In a flexbox layout, is there a `flex-shrink: 0` declaration for the cross axis?

I want to prevent elements in a flex container from shrinking in the dimension that is not the flex-direction. The following example has <article> elements side by side in a row. When the available vertical space is reduced, these elements do not force their flex container to display a scrollbar; instead the content overflows the element boundary.
Screenshot 1 - there is enough horizontal and vertical space to display everything:
Screenshot 2 - the reduced vertical space pushes the element border up:
Screenshot 3 - vertical space further reduced, container finally gets a scrollbar:
Screenshot 4 - without flex-shrink:0, the element widths (main flex axis) will also be reduced:
flex-shrink:0 can prevent horizontal shrinking, but how can I prevent the elements from shrinking vertically?
Giving the <article> elements overflow: auto or something similar does not give the desired result (= scrollbar on the container). Ideally, the display would look like this montage:
If I knew the elements' height in advance, I could give them a min-height, but that is not always the case.
FIDDLE: http://jsfiddle.net/twdan8u8/
HTML:
<main>
<article>article<br>article<br>article</article>
<article>article<br>article<br>article</article>
</main>
CSS:
* {
box-sizing: border-box; /* not the culprit */
}
html {
height: 100%;
}
body {
position: relative;
height: 100%;
margin: 0;
background: #999;
}
main {
overflow: auto;
background: gold;
display: flex;
height: 80%;
padding: 50px 30px;
}
article {
flex-shrink: 0;
font-size: 28px;
border: 2px solid red;
margin-right: 30px;
padding: 10px;
}
As is so often the case, I found the (or rather a) solution just when I finished writing the question. Since this might help somebody else, here's what I found out:
If the flex container is given the style align-items: flex-start, element heights are not reduced and the container gets a scrollbar when necessary (assuming a suitable overflow value).
The default for this property is "stretch". It can also be set on individual flex elements using align-self. The drawback is that the elements are now no longer equally high (i.e., they don't stretch to the full available height anymore).