Search code examples
csslayoutflexboxheight

CSS: When flex row wraps, how to keep scrollable column from being pushed down below its container's fold?


In a fixed-height container, I have a flexbox (.main) with flex-direction: row and flex-wrap: wrap. At a wide screen size, I'd like its children — an image (.image-column), and a long amount of text (.text-column) — to be columns, in which the image remains static and the text scrolls. So far, so good. When sized down past the columns' min widths, I'd like them to wrap and stack. This all works, but here's the problem: when they wrap and stack, the scrollable text is offset by the min-height of the .image-column above it, such that when you scroll down to the bottom of the text, the scrollbar disappears offscreen rather than remaining within its container. But if you adjust the height to account for this offset, the .text-column is squished in the unwrapped state.

The two conflicting elements seem to be:

  • .image-column's min-height: 200px — but without this, the image doesn't show up at all when wrapped
  • .text-column's height: 100% — but without this, the text doesn't scroll

I've tried .text-column with height: calc(100% - 200px) — this fixes the offset when stacked, but creates unnecessary whitespace when in columns, and I'd prefer to avoid such specificity anyway

I'd like to figure out a solution with CSS only and no media queries, since this container may be in a multi-column layout with other containers. I'm willing to use flex, grid, float, or any other arcane layout trick.

Has anyone else experienced this issue with flex row wrapping and scrolling?

(You can run the code snippet below to see the layout wrapped, and press Full Page to see the layout in its wide 2-column state.)

body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 40px;
    padding: 40px;  
    background: #f6f6f6;
    font-family: sans-serif; font-size: 120%; line-height: 1.5; color: #111;
}

.container {
    height: 600px; 
    min-width: 300px;
    max-width: 900px;
    width: 100%;
    display: flex;
    flex-direction: column;
    background-color: white;
    border-radius: 10px;
    overflow: hidden; /* don't scroll */
    border: 1px solid #ccc;
}
.header {
    border-bottom: 1px solid #ccc;
    padding: 20px;
}
.main {
    flex: 1 1 auto; /* fills remainder of height */
    display: flex;
    flex-direction: row;
    flex-wrap: wrap; /* wrap after elements reach min-width / flex-basis */
    overflow: hidden; /* don't scroll */
}
.image-column {
    min-height: 200px;
    flex: 1 1 300px; /* column width, min for wrapping */
    background-image: url('https://images.unsplash.com/photo-1641361784653-73767ccfdf60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHx0b3BpYy1mZWVkfDM0fDZzTVZqVExTa2VRfHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=900&q=60');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.text-column {
    height: 100%;
    flex: 2 1 420px; /* column width, min for wrapping */
    overflow: auto; /* scroll */
    padding: 20px;
}
.footer {
    border-top: 1px solid #ccc;
    padding: 20px;
}
    <div class="container">
        <div class="header">Header</div>
        <div class="main">
            <div class="image-column"></div>
            <div class="text-column">
                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla varius enim eu justo posuere, a laoreet urna sodales. Mauris ac porta lorem, id malesuada ipsum. In ac ipsum sapien. Nunc nisi tellus, auctor vitae placerat sit amet, posuere id velit. In semper ex placerat purus sollicitudin, at aliquet metus viverra. Nam malesuada vel tellus malesuada fermentum. Curabitur iaculis vel lacus nec scelerisque. Praesent feugiat ac augue bibendum facilisis. Curabitur dapibus malesuada magna sit amet vehicula. Fusce ipsum eros, consectetur in sodales ac, lacinia at purus. Quisque laoreet justo et semper luctus. Nam ligula orci, ultricies et enim non, elementum rutrum massa. Aenean cursus lacus in magna hendrerit, in fermentum ex porttitor. Integer elementum, enim sit amet vehicula mollis, diam tortor congue massa, eget accumsan nulla arcu a leo. Maecenas convallis placerat magna, vitae lacinia mi ullamcorper quis. Etiam congue aliquet dolor eget dignissim. Donec malesuada laoreet nibh, sit amet tristique lacus facilisis nec. Quisque sit amet enim ac lectus bibendum laoreet. Pellentesque eleifend urna eget ultricies commodo. Nulla ex sapien, accumsan sit amet est vitae, pretium sollicitudin metus. Nullam finibus, ipsum id tincidunt tristique, diam tortor pellentesque orci, non rutrum turpis arcu ultricies eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla varius enim eu justo posuere, a laoreet urna sodales. Mauris ac porta lorem, id malesuada ipsum. In ac ipsum sapien. Nunc nisi tellus, auctor vitae placerat sit amet, posuere id velit. In semper ex placerat purus sollicitudin, at aliquet metus viverra. Nam malesuada vel tellus malesuada fermentum. Curabitur iaculis vel lacus nec scelerisque. Praesent feugiat ac augue bibendum facilisis. Curabitur dapibus malesuada magna sit amet vehicula. Fusce ipsum eros, consectetur in sodales ac, lacinia at purus. Quisque laoreet justo et semper luctus. Nam ligula orci, ultricies et enim non, elementum rutrum massa. Aenean cursus lacus in magna hendrerit, in fermentum ex porttitor. Integer elementum, enim sit amet vehicula mollis, diam tortor congue massa, eget accumsan nulla arcu a leo. Maecenas convallis placerat magna, vitae lacinia mi ullamcorper quis. Etiam congue aliquet dolor eget dignissim. Donec malesuada laoreet nibh, sit amet tristique lacus facilisis nec. Quisque sit amet enim ac lectus bibendum laoreet. Pellentesque eleifend urna eget ultricies commodo. Nulla ex sapien, accumsan sit amet est vitae, pretium sollicitudin metus. Nullam finibus, ipsum id tincidunt tristique, diam tortor pellentesque orci, non rutrum turpis arcu ultricies eros.</p>
            </div>
        </div>
        <div class="footer">Footer</div>
    </div>


Solution

  • I've found a solution to my own question.

    Grid has a similar way of wrapping columns, using repeat's auto-fit with a minmax for each column in which the min is a fixed width.

    .main {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); 
        overflow: hidden; /* don't scroll */
    }
    

    Using this approach, when the columns wrap, the first column's min-height no longer offsets the second column and its scrollbar out of the container, as it did with flex row wrap.

    The only small drawback is that the grid's two columns must be of equal width in order for the wrapping to work. For my uses, this is acceptable.

    body {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 40px;
        padding: 40px;  
        background: #f6f6f6;
        font-family: sans-serif; font-size: 120%; line-height: 1.5; color: #111;
    }
    
    .container {
        height: 600px; 
        min-width: 300px;
        max-width: 900px;
        width: 100%;
        display: flex;
        flex-direction: column;
        background-color: white;
        border-radius: 10px;
        overflow: hidden; /* don't scroll */
        border: 1px solid #ccc;
    }
    .header {
        border-bottom: 1px solid #ccc;
        padding: 20px;
    }
    .main {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); 
        overflow: hidden; /* don't scroll */
    }
    .image-column {
        min-height: 200px;
        background-image: url('https://images.unsplash.com/photo-1641361784653-73767ccfdf60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHx0b3BpYy1mZWVkfDM0fDZzTVZqVExTa2VRfHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=900&q=60');
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
    }
    .text-column {
        overflow: auto; /* scroll */
        padding: 20px;
    }
    .footer {
        border-top: 1px solid #ccc;
        padding: 20px;
    }
    <div class="container">
            <div class="header">Header</div>
            <div class="main">
                <div class="image-column"></div>
                <div class="text-column">
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla varius enim eu justo posuere, a laoreet urna sodales. Mauris ac porta lorem, id malesuada ipsum. In ac ipsum sapien. Nunc nisi tellus, auctor vitae placerat sit amet, posuere id velit. In semper ex placerat purus sollicitudin, at aliquet metus viverra. Nam malesuada vel tellus malesuada fermentum. Curabitur iaculis vel lacus nec scelerisque. Praesent feugiat ac augue bibendum facilisis. Curabitur dapibus malesuada magna sit amet vehicula. Fusce ipsum eros, consectetur in sodales ac, lacinia at purus. Quisque laoreet justo et semper luctus. Nam ligula orci, ultricies et enim non, elementum rutrum massa. Aenean cursus lacus in magna hendrerit, in fermentum ex porttitor. Integer elementum, enim sit amet vehicula mollis, diam tortor congue massa, eget accumsan nulla arcu a leo. Maecenas convallis placerat magna, vitae lacinia mi ullamcorper quis. Etiam congue aliquet dolor eget dignissim. Donec malesuada laoreet nibh, sit amet tristique lacus facilisis nec. Quisque sit amet enim ac lectus bibendum laoreet. Pellentesque eleifend urna eget ultricies commodo. Nulla ex sapien, accumsan sit amet est vitae, pretium sollicitudin metus. Nullam finibus, ipsum id tincidunt tristique, diam tortor pellentesque orci, non rutrum turpis arcu ultricies eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla varius enim eu justo posuere, a laoreet urna sodales. Mauris ac porta lorem, id malesuada ipsum. In ac ipsum sapien. Nunc nisi tellus, auctor vitae placerat sit amet, posuere id velit. In semper ex placerat purus sollicitudin, at aliquet metus viverra. Nam malesuada vel tellus malesuada fermentum. Curabitur iaculis vel lacus nec scelerisque. Praesent feugiat ac augue bibendum facilisis. Curabitur dapibus malesuada magna sit amet vehicula. Fusce ipsum eros, consectetur in sodales ac, lacinia at purus. Quisque laoreet justo et semper luctus. Nam ligula orci, ultricies et enim non, elementum rutrum massa. Aenean cursus lacus in magna hendrerit, in fermentum ex porttitor. Integer elementum, enim sit amet vehicula mollis, diam tortor congue massa, eget accumsan nulla arcu a leo. Maecenas convallis placerat magna, vitae lacinia mi ullamcorper quis. Etiam congue aliquet dolor eget dignissim. Donec malesuada laoreet nibh, sit amet tristique lacus facilisis nec. Quisque sit amet enim ac lectus bibendum laoreet. Pellentesque eleifend urna eget ultricies commodo. Nulla ex sapien, accumsan sit amet est vitae, pretium sollicitudin metus. Nullam finibus, ipsum id tincidunt tristique, diam tortor pellentesque orci, non rutrum turpis arcu ultricies eros.</p>
                </div>
            </div>
            <div class="footer">Footer</div>
        </div>

    (You can run the code snippet below to see the layout wrapped, and press Full Page to see the layout in its wide 2-column state.)