I am trying to build a page layout with a standard structure: a Header, a Footer, and a main content area that fills the available vertical space.
The Goal
Inside the main content area, I want to create "full-screen" scrollable sections. The desired behavior is:
- The first section (
.screen-1) should take up exactly 100% of the visible height of its scrolling container (.main-content-wrapper). - Any subsequent content (
.screen-2, etc.) should be positioned directly below the first section. - The user must scroll within the main content area to see the subsequent content.
Essentially, I'm aiming for a "full-page" slide effect inside a specific part of the layout, not the whole browser window.
My Layout Structure
My page is built with Flexbox.
- A top-level
.layout-containerusesdisplay: flexandflex-direction: columnto arrange the Header, Main Content, and Footer. - The
.main-content-wrapperhasflex: 1so it grows to fill the space between the header and footer. This is the element that should have its own scrollbar when content overflows. - Inside it,
.page-main-contentholds the actual sections.
What I've Tried and The Problem
The core issue is that I cannot get .screen-1 to be 100% of its parent's height. It seems the height of the flex: 1 container isn't "defined" in a way that allows its children to use percentage-based heights effectively.
Here are my attempts:
Using
flex: 1on the child: My first thought was to applyflex: 1to.screen-1. I expected this would make it greedily take up all available space, pushing.screen-2out of view.- Actual Result: Instead of filling the container,
.screen-1only takes up the remaining space after.screen-2has taken up space for its own content. Both sections remain visible simultaneously.
- Actual Result: Instead of filling the container,
Using
height: 100%: I also tried settingheight: 100%ormin-height: 100%on.screen-1.- Actual Result: This had no effect. The element collapsed to the height of its inner content, likely because its flex parent (
.page-main-content) doesn't have a specific height for the percentage to be based on.
- Actual Result: This had no effect. The element collapsed to the height of its inner content, likely because its flex parent (
Here is a Minimal Reproducible Example (MRE) that demonstrates my setup and the issue.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Flexbox Height Problem MRE</title><style> /* Basic Reset & Body Setup */ * { box-sizing: border-box; margin: 0; } html, body { height: 100%; font-family: sans-serif; } /* This is the main layout container, similar to my `layouts/default.vue`. It arranges Header, Main Content, and Footer vertically. */ .layout-container { display: flex; flex-direction: column; min-height: 100vh; background-color: #f0f2f5; } .app-header { padding: 1rem; background-color: #e3f2fd; border-bottom: 1px solid #bbdefb; text-align: center; } .app-footer { padding: 1rem; background-color: #e8f5e9; border-top: 1px solid #c8e6c9; text-align: center; } /* This is the main content area that should fill the remaining space. It has `flex: 1`. This is the container I want to have its own scrollbar. */ .main-content-wrapper { flex: 1; display: flex; /* This is crucial, it allows its child to grow */ flex-direction: column; /* For demonstration: show the boundary and enable scrolling if content overflows */ overflow-y: auto; border: 2px dashed red; } /* This represents my "page" component, like `pages/index.vue`. It's inside the main content wrapper. */ .page-main-content { /* My initial attempt uses flex: 1 here */ flex: 1; display: flex; flex-direction: column; } /* --- THE PROBLEM IS HERE --- */ .screen { display: grid; place-items: center; padding: 2rem; font-size: 1.5rem; font-weight: bold; color: white; } /* This is "Screen 1" */ .screen-1 { /* This is what I tried. It doesn't make the screen full-height. */ /* It just takes up the *remaining* space. */ flex: 1; background-color: #3f51b5; } /* This is "Screen 2" */ .screen-2 { /* This screen just takes space for its content, but it's still visible. */ background-color: #43a047; min-height: 150px; /* Give it some height for the demo */ }</style></head><body><div class="layout-container"><header class="app-header"><h1>Header</h1></header><div class="main-content-wrapper"><main class="page-main-content"><div class="screen screen-1"><p>Screen 1</p><p style="font-size: 1rem; font-weight: normal;">I want this section to be 100% the height of the red dashed container.</p></div><div class="screen screen-2"><p>Screen 2</p><p style="font-size: 1rem; font-weight: normal;">I should only be visible after scrolling down inside the red dashed container.</p></div></main></div><footer class="app-footer"><p>Footer</p></footer></div></body></html>My question is: What is the correct CSS approach to make .screen-1 occupy 100% of the height of its scrollable container (.main-content-wrapper), thereby forcing .screen-2 to be pushed down and become visible only after scrolling?
If possible, please try to provide a solution that doesn't change the nested HTML structure in the MRE. This is a key constraint for me, as changing it would break my existing layout. 🫠
That said, I'm not entirely sure if my HTML structure is the correct approach to begin with. For full context, I've also attached my original Vue code below for reference.
Thank you in advance for any help
<template><div class="layout relative isolate"><div ref="bgLayerAEl" class="fixed inset-0 -z-10 h-[100dvh] bg-cover bg-fixed bg-center opacity-100" /><div ref="bgLayerBEl" class="fixed inset-0 -z-10 h-[100dvh] bg-cover bg-fixed bg-center opacity-0" /><div class="flex min-h-screen flex-col pb-12"><AppHeader ref="headerCmp" class="sticky top-0 pb-0!" :class="[{'mb-2 p-2 md:mb-3 md:p-3 lg:mb-4 lg:p-4': spacing === 'small','mb-4 p-4 md:mb-6 md:p-6 lg:mb-8 lg:p-8': spacing === 'medium','mb-8 p-8 md:mb-12 md:p-12 lg:mb-16 lg:p-16': spacing === 'large', }]" /><div class="flex flex-1 flex-col overflow-y-auto" :class="{'px-2 md:px-3 lg:px-4': spacing === 'small','px-4 md:px-6 lg:px-8': spacing === 'medium','px-8 md:px-12 lg:p-16': spacing === 'large', }"><slot /></div></div><AppFooter /></div></template><template><!-- will be into <slot /> --><main><div class="text-2xl"> Screen 1</div><div class="text-2xl"> Screen 2</div></main></template>