CSSIntermediate

Sticky Headers and Classic Page Layout Patterns in CSS

Use position: sticky for headers and sidebars, then build the centered container, sidebar layout and holy grail page with a footer that stays at the bottom.

All CSS lessons

What you will learn

You now know enough CSS to style individual pieces. This lesson puts the pieces together into whole-page layouts. First you will learn position: sticky, the property behind headers that follow you down the page and sidebars that stay in view. Then you will build the three layouts that cover most websites: the centered container, the sidebar layout and the “holy grail” (header, footer and three columns), complete with a footer that always sits at the bottom of the window.

A quick note before we start: layouts are really the job of Flexbox and Grid, which have their own lessons later in the course. Here we use just a few lines of Flexbox, explained in the box below, so that we can build real pages now. You do not need to master it yet.

Flexbox in 60 seconds

Put display: flex on a parent, and its children line up side by side in a row.

  • justify-content spreads the children along the row (space-between pushes the first to one end and the last to the other).
  • align-items lines them up vertically (center centres them).
  • gap adds space between children.
  • On a child, flex: 1 means “take all the remaining space”, and flex: 0 0 200px means “stay exactly 200px wide”.
  • flex-direction: column stacks the children vertically instead.

That is all we need for this lesson.

The HTML we will style

<div class="page">

  <header class="site-header">
    <div class="container header-inner">
      <strong class="logo">MyCodeNest</strong>
      <nav class="top-nav">
        <a href="#home" class="top-link">Home</a>
        <a href="#courses" class="top-link">Courses</a>
        <a href="#about" class="top-link">About</a>
      </nav>
    </div>
  </header>

  <div class="layout container">

    <nav class="sidebar">
      <h2 class="sidebar-title">Lessons</h2>
      <ul class="sidebar-list">
        <li><a href="#l1">Introduction</a></li>
        <li><a href="#l2">Selectors</a></li>
        <!-- ... -->
      </ul>
    </nav>

    <main class="main">
      <h1>Holy Grail Layout</h1>
      <p>...many paragraphs so the page can scroll...</p>
    </main>

    <aside class="aside">
      <h2 class="aside-title">On this page</h2>
      <ul class="aside-list"> ... </ul>
    </aside>

  </div>

  <footer class="site-footer">
    <div class="container">© 2026 MyCodeNest</div>
  </footer>

</div>

The structure as a tree:

div.page
├── header.site-header
│   └── div.container.header-inner
│       ├── strong.logo
│       └── nav.top-nav  (3 links)
├── div.layout.container           <- the three columns live here
│   ├── nav.sidebar                (left column)
│   ├── main.main                  (middle column, fluid)
│   └── aside.aside                (right column)
└── footer.site-footer
    └── div.container
Selector Element Job
.page the outer <div> whole-page wrapper, used for the sticky footer trick
.container inner <div>s in the header, layout and footer centers content and limits its width
.site-header <header> sticky top bar
.header-inner <div> inside the header lays out the logo and the nav in a row
.layout <div> holding the columns the flex row
.sidebar, .main, .aside <nav>, <main>, <aside> left, middle and right columns
.site-footer <footer> bottom bar

Notice that layout and container are two classes on one element. Each class does one job: container limits the width, layout arranges the columns.

Pattern 1: the centered container

Text that stretches across a 1920px monitor is unreadable. The fix is a wrapper with a maximum width, centered with margin: 0 auto (from the box model lesson):

.container {
  max-width: 1000px;
  margin: 0 auto;
  padding-left: 16px;
  padding-right: 16px;
}
  • max-width lets the box shrink on small screens, where a fixed width would overflow.
  • margin: 0 auto centers it horizontally.
  • The side padding keeps the content off the screen edges on phones.
  • We write padding-left and padding-right separately, so that other rules can add top and bottom padding to the same element without fighting this one.

Put the same .container inside the header, the main area and the footer, and their content lines up perfectly, while their backgrounds can still stretch edge to edge.

position: sticky

position: sticky is a clever mix of relative and fixed. The element sits in the normal flow, and scrolls with the page, until it would leave the screen. At that point it “sticks” at the offset you choose, and stays there as long as its parent is visible.

A sticky header

/* the <header class="site-header"> */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: white;
  border-bottom: 1px solid #dddddd;
}

Every line has a job:

  • position: sticky switches the behaviour on.
  • top: 0 is the threshold: stick when the element’s top edge reaches the top of the screen. Without one of top, bottom, left or right, sticky does nothing.
  • z-index: 10 keeps the header above the content that scrolls beneath it.
  • background-color stops the scrolling content showing through.

And the row inside the header, using the flexbox basics:

/* the <div class="container header-inner"> */
.header-inner {
  display: flex;
  justify-content: space-between;   /* logo left, nav right */
  align-items: center;              /* vertically centered */
  height: 64px;
}

/* the links in <nav class="top-nav"> */
.top-link {
  margin-left: 16px;
  color: royalblue;
  text-decoration: none;
}

Sticky vs fixed

sticky fixed
Takes space in the layout Yes No, it leaves the flow
Position Relative to its scrolling parent Relative to the window
Stops at the end of its parent Yes No, it stays forever
Good for headers, sidebars, table headers chat buttons, cookie banners

A sticky sidebar

/* the <nav class="sidebar"> */
.sidebar {
  position: sticky;
  top: 88px;              /* header height (64px) + a 24px gap */
  align-self: flex-start;
  flex: 0 0 200px;
}

Two details make this work:

  • top: 88px leaves room below the sticky header. If you used top: 0, the sidebar would slide under the header.
  • align-self: flex-start is essential. By default, a flex child stretches to the full height of its row, so the sidebar is exactly as tall as the row and has no space to move inside it. flex-start makes it only as tall as its content, which leaves room for it to slide.

Why is my sticky element not sticking?

This is the number-one sticky question. Work through this checklist:

  1. Did you set a threshold (top: 0, or similar)?
  2. Does any ancestor have overflow: hidden, auto or scroll? That ancestor becomes the scroll container, and the element sticks to it instead of the window. Remove the overflow or restructure.
  3. Is the parent as tall as the sticky element? Sticky only moves inside its parent. In flex or grid, stretch causes this. Use align-self: flex-start (or start in grid).
  4. Is there a background and z-index? Otherwise it may stick but be covered, or look transparent.

Sticky table headers

The same property keeps a table’s header row visible in a scrolling table:

th {
  position: sticky;
  top: 0;
  background-color: white;
}

When a visitor clicks a link to #section, the browser scrolls that section to the very top, behind your sticky header. One line on the html element fixes it:

html {
  scroll-padding-top: 80px;   /* a bit more than the header height */
}

Pattern 2: the sidebar layout

Two columns: a fixed-width sidebar and a fluid main area. This is the layout of documentation sites, dashboards and many course websites.

/* the <div class="layout container"> that holds the columns */
.layout {
  display: flex;
  gap: 24px;
  padding-top: 24px;
  padding-bottom: 24px;
}

.sidebar {
  flex: 0 0 200px;     /* do not grow, do not shrink, exactly 200px wide */
}

.main {
  flex: 1;             /* take all the remaining space */
  min-width: 0;        /* lets wide content (code blocks, tables) shrink instead of overflowing */
}

flex: 0 0 200px means grow 0, shrink 0, start at 200px, so the sidebar always stays 200px. The .main column gets everything that remains. The min-width: 0 line is a small but important safety net: without it, a wide code block or table inside .main would push the whole layout sideways.

Pattern 3: the holy grail

The name comes from a long-standing challenge: a header and footer, three columns (navigation, content, extras), with the middle column fluid, and the footer pinned to the bottom of the window even when there is little content. Flexbox makes it simple. You only need to add the right column to the sidebar layout:

.aside {
  flex: 0 0 180px;     /* the right column, fixed width */
}

With .sidebar at 200px, .aside at 180px and .main using flex: 1, the middle column is fluid.

If the page has only a few lines of content, the footer should still sit at the bottom of the window rather than just under the text. The trick is to turn the outer .page wrapper into a full-height column, and let the layout area grow:

/* the outer <div class="page"> */
.page {
  min-height: 100vh;           /* at least as tall as the window */
  display: flex;
  flex-direction: column;      /* header, layout and footer stack vertically */
}

/* the <div class="layout container"> grows to fill the free space */
.layout {
  flex: 1;
}

/* the <footer class="site-footer"> */
.site-footer {
  background-color: #222222;
  color: white;
  padding: 20px 0;
  text-align: center;
}

min-height: 100vh makes the page at least as tall as the window. Inside it, .layout has flex: 1, so it absorbs any spare height and pushes the footer to the bottom. When the content is long, the page simply grows and the footer follows the content.

Modern alternative: CSS Grid can build the whole holy grail in one line: grid-template-columns: 200px 1fr 180px;. You will learn it in the Grid lessons. The Flexbox version you just saw is worth knowing because it is everywhere.

A peek at responsive design

Three columns are too much for a phone. A media query applies CSS only when a condition is true, such as “the screen is narrower than 700px”. Here it stacks the columns:

@media (max-width: 700px) {
  .layout {
    flex-direction: column;     /* columns stack vertically */
  }

  .sidebar {
    position: static;           /* no sticky sidebar on small screens */
    flex-basis: auto;
  }

  .aside {
    flex-basis: auto;
  }
}

You will study media queries properly in the Responsive Design lesson. The point for now is that the same HTML can become a single column on a phone.

Putting it together

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.6;
}

html { scroll-padding-top: 80px; }

.container {
  max-width: 1000px;
  margin: 0 auto;
  padding-left: 16px;
  padding-right: 16px;
}

.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: white;
  border-bottom: 1px solid #dddddd;
}

.header-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 64px;
}

.top-link {
  margin-left: 16px;
  color: royalblue;
  text-decoration: none;
}

.layout {
  flex: 1;
  display: flex;
  gap: 24px;
  padding-top: 24px;
  padding-bottom: 24px;
}

.sidebar {
  flex: 0 0 200px;
  align-self: flex-start;
  position: sticky;
  top: 88px;
}

.main {
  flex: 1;
  min-width: 0;
}

.aside {
  flex: 0 0 180px;
  align-self: flex-start;
}

.site-footer {
  background-color: #222222;
  color: white;
  padding: 20px 0;
  text-align: center;
}

@media (max-width: 700px) {
  .layout { flex-direction: column; }
  .sidebar { position: static; flex-basis: auto; }
  .aside { flex-basis: auto; }
}

Common mistakes

  • Using sticky without a top (or other) offset. It does nothing.
  • An ancestor with overflow: hidden, auto or scroll, which silently breaks sticky.
  • Forgetting align-self: flex-start on a sticky flex child, so it has no room to move.
  • A sticky header without a background or z-index, so content shows through or slides on top of it.
  • Setting top: 0 on a sidebar that sits below a sticky header, so the header covers it.
  • Using width instead of max-width for the container. A fixed width causes sideways scrolling on phones.
  • Using height: 100vh for the page instead of min-height. Long content then overflows the page.
  • Forgetting that anchor links hide under the header. Add scroll-padding-top.
  • Keeping three fixed columns on phones. Plan a simple media query (or use Grid and Flexbox features you will learn soon).

Practice

  1. Click Practice in Editor. Scroll the preview. Does the header stay in view? Change position: sticky to position: relative and scroll again to see the difference.
  2. Add the .layout, .sidebar, .main and .aside rules step by step, one column at a time, and look at the page after each step.
  3. Make the sidebar sticky with top: 88px. Then remove align-self: flex-start and see what happens.
  4. Change the header to position: fixed. What happens to the content at the top of the page?
  5. Shorten the page by deleting most of the paragraphs. Does the footer stay at the bottom? Remove min-height: 100vh from .page and check again.
  6. Add a media query at max-width: 700px that stacks the columns, and test it by resizing the preview.
  7. Wrap .sidebar in a <div style="overflow: hidden"> temporarily, and watch sticky stop working. Then remove it.

Recap

  • position: sticky needs an offset like top: 0, sticks within its parent, and breaks if an ancestor has overflow set.
  • For sticky flex children, add align-self: flex-start. For sticky headers, add a background and a z-index.
  • .container { max-width; margin: 0 auto; } is the foundation of nearly every page.
  • The sidebar layout is a flex row with a fixed column (flex: 0 0 200px) and a fluid one (flex: 1).
  • The holy grail adds a right column and a footer, and uses min-height: 100vh with a flex column so the footer stays at the bottom.
  • A media query can collapse columns on small screens.
  • You now have the tools to build complete page shells. Next come the Flexbox and Grid lessons, which explain the layout properties you used here properly.