HTML fixed header and footer

A grid with three rows and a scrolling middle beats two fixed boxes. It reserves the space by construction instead of by padding you have to keep in sync.

The reliable way to build an HTML fixed header and footer is a three row CSS grid on the body, not two elements with position: fixed.

body {
  margin: 0;
  height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}
main { overflow: auto; min-height: 0; }

The grid reserves the header and footer rows by construction. There is no padding to keep in sync with a header height that changes when someone edits the title.

A page with a bar at the top, a bar at the bottom, and the middle section scrolled.
A page with a bar at the top, a bar at the bottom, and the middle section scrolled.
Grid rows Two fixed boxes
Space reserved for the bars Automatic Manual padding
Survives a header height change Yes No, padding goes stale
Scroll position preserved on resize Yes Yes
Anchor links land correctly Yes Need scroll-margin-top
Works inside a nested container Yes Fixed escapes to the viewport

The second row is the one that bites later. A fixed header padded by 4rem looks right until someone adds a second line to the title, and then every page has content hidden behind the bar.

The two lines people miss

min-height: 0. A grid item's default minimum size is its content, so a 1fr row refuses to shrink below the content height and the scrollbar never appears. Setting min-height: 0 lets the row shrink and scroll.

100dvh rather than 100vh. On phones the browser toolbar appears and disappears while scrolling. 100vh refers to the larger state, so a 100vh layout is taller than the visible area and the footer sits below the fold.

100dvh follows the current visible height. Keep a fallback for older engines:

body { height: 100vh; height: 100dvh; }

None of this works at all without the viewport meta tag in the head.

The position fixed version

Sometimes the page is not yours to restructure and two fixed boxes are the only option.

header { position: fixed; top: 0; left: 0; right: 0; height: 3.5rem; background: #0e1014; }
footer { position: fixed; bottom: 0; left: 0; right: 0; height: 3rem; background: #0e1014; }
body  { padding: 3.5rem 0 3rem; }

Three things to add when you do this.

The padding values must match the heights exactly, so declare both from custom properties rather than typing the numbers twice.

Anchor targets need scroll-margin-top equal to the header height, the same fix described in sticky headers.

And on a phone, the footer overlaps the browser's own bottom bar in some states. bottom: env(safe-area-inset-bottom) helps on devices with a home indicator.

The same layout at phone width, with the footer resting on the bottom edge.
The same layout at phone width, with the footer resting on the bottom edge.

When you want sticky instead

Fixed and grid both pin the bars permanently. Often that is more than the page needs.

A header that scrolls away and comes back when you scroll up keeps the full screen for reading. position: sticky with top: 0 gives you the simpler half of that with two lines and no reserved space.

Use fixed or grid when:

  • The footer holds actions such as Save and Cancel that must always be reachable.
  • The header holds a filter that applies to the scrolling content.
  • The page is a tool rather than a document.

Use sticky when the page is something to read. A reader on a small screen values the pixels more than the permanent bar.

The keyboard on a phone

A fixed footer holding buttons sits above the on-screen keyboard on some devices and underneath it on others.

The reliable fix is to stop fixing it while an input has focus.

@media (max-height: 26rem) {
  footer { position: static; }
}

A short viewport height is a good proxy for the keyboard being open, and it needs no script. The footer rejoins the flow, the reader scrolls to it, and nothing is covered.

The alternative is the visual viewport API, which reports the real visible area. It is more precise and it is more code, so reach for it only if the media query is not enough.

The same grid handles a toolbar between the header and the content, or a status strip above the footer. Add rows rather than nesting containers.

grid-template-rows: auto auto 1fr auto;

Keep exactly one 1fr row. Two flexible rows means two scroll areas, and the reader has to work out which one their wheel is going to move.

Name the rows if the layout grows past four, because positional grid-row numbers become unreadable quickly. CSS grid covers named areas.

A different and more common requirement: the footer should sit at the bottom of the window when the page is short, and below the content when the page is long.

The same grid does it with no changes. With height: 100dvh and grid-template-rows: auto 1fr auto, a short page leaves the 1fr row empty and the footer sits at the bottom.

If you want the page to grow past the window instead, use min-height: 100dvh and drop overflow: auto from the middle row.

Printing

Fixed elements print once, and browsers disagree about where. A fixed footer can land in the middle of page one, or vanish.

@media print {
  body { display: block; height: auto; }
  header, footer { position: static; }
  main { overflow: visible; }
}

Resetting all three is necessary. Leaving overflow: auto on the scrolling region means only the visible portion prints, which is the version of this bug that reaches the reader before it reaches you.

Print stylesheets cover page breaks and link handling.

Checking it

Print preview of the layout, with the bars in the flow and the full content visible.
Print preview of the layout, with the bars in the flow and the full content visible.

Open the file in the HTML file opener and resize the window from full height down to about 400 pixels. The footer should stay at the bottom edge at every size.

Open the print preview and confirm the whole document is there, not just the first screen. Then narrow to phone width and scroll until the browser toolbar hides.

When it holds up, paste the HTML into a NOS document. The layout renders at the document's own address and the reader opens a link. The bar contents themselves are covered in navbars.

Questions people ask

How do I fix a header and footer in HTML and CSS?

Make the body a grid with three rows sized auto, 1fr and auto, and give the middle row overflow auto. The header and footer stay put, the middle scrolls, and no padding compensation is needed.

Why does my content hide behind the fixed header?

A fixed element is removed from the document flow, so the content starts at the top of the page as if the header were not there. You have to add padding equal to the header height, or use a layout that reserves the row instead.

Why does the fixed footer sit wrong on a phone?

Mobile browser toolbars change the visible height while you scroll, and 100vh refers to the larger of the two. Use 100dvh, the dynamic viewport height, so the layout follows the toolbar.

Do fixed headers and footers print on every page?

No. A fixed element usually prints once, and browsers differ on which page it lands. For printed output set both to position static so they appear in their natural place in the flow.

Keep reading