Footer bugs mostly turn up on pages with very little content. The footer floats halfway up the screen with blank space under it, or it slides below the fold on a phone. A footer element plus a few layout rules on the wrapper handles both. A wrapper set to display: grid with grid-template-rows: auto 1fr auto, or a flex column whose main area grows, needs no scripting.
Where the element sits changes what it means. MDN Web Docs (2026) lists its implicit role as contentinfo by default, and as generic when it sits inside article, aside, main, nav or section.
Site-wide templates render the footer on every URL, so one layout or markup mistake repeats across the whole site.
How do you build a footer with HTML and CSS?
Put a footer element after the main content, then give it a background-color, padding and text-align in CSS.
- Add the footer element as a direct child of body, after main.
- Fill it with a nav of links and a copyright line, plus contact details if the site needs them (an address element suits author or contact info).
- Set box-sizing to border-box on every element and reset the body margin to 0, so padding adds no stray width.
- Style the footer with background-color, color, padding and text-align.
- Give the links a hover state, a :focus-visible outline and enough spacing to tap.
<body>
<main>...</main>
<footer>
<nav aria-label="Footer">
<a href="/privacy">Privacy</a>
<a href="/contact">Contact</a>
</nav>
<p>Copyright 2026 Example Studio</p>
</footer>
</body>*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
footer { background-color: #1f2933; color: #f5f7fa; padding: 24px 16px; text-align: center; }
footer a { color: inherit; }
footer a:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }Nothing in that skeleton goes past standard HTML, and the stylesheet is short.
MDN’s footer reference lists the element as available across browsers since July 2015, so no fallback markup is needed.
Footer markup that gets the contentinfo role
A footer that isn’t nested inside article, aside, main, nav or section maps to the contentinfo landmark, and MDN advises making it an immediate descendant of body. Each page gets one top-level contentinfo landmark, per MDN’s ARIA contentinfo page. Footer and header elements aren’t allowed as descendants of a footer.
Before Safari 13, VoiceOver did not expose the role from the element alone. Add role=”contentinfo” only if that legacy support is still on the requirements list.
Footer inside an article or section
A footer nested in article, aside, main, nav or section is not a page footer.
MDN lists its implicit role as generic in those cases, so it can carry a byline, date or related links for that block without creating a second landmark. Screen reader users don’t get a landmark shortcut to it, which is fine for byline metadata.
How do you keep a footer at the bottom of a short page?
Turn the page wrapper into a column flex container or a 3-row grid with a minimum height equal to the viewport, and let the main area take the leftover space. The footer then sits at the viewport bottom on short pages and below the content on long ones.
| Technique | Key declarations | Footer height must be known | Best fit |
|---|---|---|---|
| Flexbox, flex-grow | Column flex wrapper, flex-grow: 1 on main | No | Wrapper is already a flex column |
| Flexbox, auto margin | margin-top: auto on the footer | No | One footer rule, no main selector |
| Grid, 3 rows | grid-template-rows: auto 1fr auto | No | Header, main and footer page frame |
| Negative margin (legacy) | margin-bottom equal to footer height | Yes | Very old browsers only |
Flexbox method
Make the body the wrapper and stack its children in a column. Main then grows to fill whatever space is left.
body { min-height: 100vh; display: flex; flex-direction: column; }
main { flex-grow: 1; }
footer { flex-shrink: 0; }MDN’s recipe sets flex-shrink: 0 on the header and footer so they never compress when content fills the main area. An auto top margin on the footer gives the same result without touching main, because auto margins absorb free space in a flex container.
On phones, 100vh has a toolbar problem, which the viewport units section below deals with.
Grid method
MDN’s Sticky footers recipe uses auto, 1fr and auto for the rows.
body { min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; }Grid auto-placement drops the header into the first auto track, main into the 1fr track and the footer into the last auto track. The 1fr track soaks up the free space.
An extra direct child (a skip link, a cookie banner container) shifts every item one track down, and the 1fr row ends up holding the wrong element.
Which method to pick
For a full page frame I’d go with grid, as long as header, main and footer are the only children of the wrapper. If the wrapper is already a flex column, or script adds and removes children, flexbox is the safer pick. And when one footer rule is all the stylesheet should change, margin-top: auto does it.
Legacy methods to skip
The negative margin technique hard-codes the footer height in the stylesheet. When footer text wraps on a narrow screen, the footer outgrows that number and overlaps the content.
Absolute positioning with body padding has the same weakness. Flexbox and grid carry no such dependency.
Fixed, absolute and sticky footers: when each one fits
Keep the footer in normal flow by default. Reach for position: fixed only when it must stay on screen during scrolling, such as a consent bar or a persistent action bar.
Fixed footer
A fixed footer stays visible at any scroll position and positions against the viewport, so no wrapper height is needed.
The downsides are practical. It leaves normal flow, so it covers the last content block until the body gets padding-bottom equal to the footer height. Any ancestor with transform, perspective or filter becomes the containing block instead of the viewport (MDN). It can also obscure content when users enlarge text, which MDN flags as an accessibility risk for fixed and absolute elements.
MDN also suggests will-change: transform on fixed or sticky elements that cause repaint jank while scrolling.
Absolute footer
An absolute footer anchors to its closest positioned ancestor. Without one, it anchors to the initial containing block, which has the dimensions of the viewport and sits at the top of the document.
So bottom: 0 places the footer at the bottom of the first screen on a long page, behind the content. That is one cause of a footer floating mid-page.
It works with a known footer height and a positioned wrapper, and needs no extra markup. The catch is padding-bottom on the wrapper equal to the footer height, and the whole setup breaks when footer text wraps.
Sticky footer with position: sticky
Why 100vh breaks footer layouts on mobile, and what replaces it
On phones, 100vh matches the page with the browser toolbars retracted, so a wrapper with min-height: 100vh is taller than the visible area on load. The footer lands below the fold until the user scrolls.
The replacement is 100dvh, or 100svh when stability matters more than filling every pixel. Both come from web.dev’s viewport units article (Chrome team, last updated November 29, 2022).
Mobile browsers resize the visible viewport as the address bar and tab bar expand and retract, while vh stays fixed. svh covers the small viewport, with toolbars expanded. lvh covers the large one, toolbars retracted, and 100vh behaves like this. dvh equals svh while toolbars are expanded and lvh once they retract.
body { min-height: 100vh; min-height: 100dvh; }Browsers that cannot parse dvh drop the second declaration and keep the first.
For a footer wrapper, that pair is the default. Switch to 100svh if the footer visibly jumps while toolbars animate, since the small viewport values stay fixed unless the viewport itself resizes.
Support, per web.dev in November 2022, starts at version 108 in Chrome and Edge, 101 in Firefox and 15.4 in Safari. Dynamic viewport values also update in a throttled way, below 60fps. Check Can I use before dropping the 100vh fallback, because those versions date from 2022.
MDN’s own recipe takes a different route: html and body at height 100%, with a wrapper at min-height: 100%, which avoids viewport units entirely. Its note offers min-height: 100vh on body as the alternative, and that alternative inherits the mobile overshoot.
Some third-party guides claim browsers disagree on what vh measures. web.dev, the Chrome team’s own article, treats vh as a fixed size that overshoots while toolbars are expanded.
The on-screen keyboard does not resize any of these units unless Chrome’s opt-in behavior is switched on. A footer sized with dvh stays put when a form field opens the keyboard.
For a wider look at vw, vmin and the newer units, see this guide to CSS viewport units.
How do you build a multi-column footer that stacks on small screens?
Give the columns a grid with repeat(auto-fit, minmax(…)) tracks and a gap, and they wrap to fewer columns as the width shrinks. Add a media query only when the design needs an exact column count at a given width.
:root { --footer-gap: 32px; }
.footer-columns {
display: grid;
gap: var(--footer-gap);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}Custom properties keep the gap, colors and spacing in one place, so the columns and the icon strip never drift apart.
Grid columns
Grid keeps column edges aligned across rows, which suits link groups under headings.
With auto-fit, empty tracks collapse, so 3 columns in a 4-track row stretch to fill it. With auto-fill they stay reserved, and the columns hold the minmax minimum width.
For a fixed count, write repeat(4, minmax(0, 1fr)). Plain 1fr cannot shrink below its content, so a long email address or URL widens its column and pushes the page sideways.
Flexbox columns
Flex wrap works, but every wrapped row sizes itself.
.footer-columns { display: flex; flex-wrap: wrap; gap: var(--footer-gap); }
.footer-columns > * { flex: 1 1 200px; }A lone column on the last row stretches across the full width, so its edges stop lining up with the rows above.
Flexbox earns its place in the one-row parts of a footer, like the social icon strip and the legal link line.
Stacking rules
Write the stacked layout as the default, then widen it with min-width queries, as mobile-first design prescribes.
.footer-columns { grid-template-columns: 1fr; }
@media (min-width: 640px) {
.footer-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 960px) {
.footer-columns { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}Those two media queries are placeholders. Resize the window until link text starts wrapping, then set the breakpoint there instead of at a device width.
Tab order follows the DOM, so the column a keyboard user should reach first has to come first in the markup.
Footer accessibility: contrast, focus, landmark labels and target size
A footer can fail WCAG 2.2 in several unrelated ways, so each of these gets its own check.
Text contrast
W3C’s Understanding document for SC 1.4.3 (updated September 2026) sets the Level AA minimum at 4.5:1 for normal text and 3:1 for large text, meaning 18 point or 14 point bold.
Hover and keyboard focus states count, because the criterion covers text revealed on hover or focus. Brand guidelines are no exemption, since only logos and logotype text are exempt. Set color and background-color together as well, because W3C lists specifying one without the other as failure technique F24.
Test each pair in a checker, and read this primer on color contrast if the ratio math is new.
Keyboard focus behind a pinned footer
A pinned footer can hide the very link that has keyboard focus.
WCAG 2.2 SC 2.4.11, Focus Not Obscured (Minimum), is Level AA and names sticky footers as a typical offender. W3C documents CSS scroll-padding as the sufficient technique (C43) and lists a footer that completely hides a focused element as failure F110.
:root { --footer-height: 72px; }
footer { position: fixed; inset: auto 0 0; height: var(--footer-height); }
body { padding-bottom: var(--footer-height); }
html { scroll-padding-bottom: var(--footer-height); }Body padding clears the last block of content, while scroll-padding keeps tabbed elements out from under the footer at every scroll position.
The footer height lives in a variable now, so a taller footer on mobile needs the variable changed in its media query too.
Landmark labels
When a document has more than one contentinfo or footer, MDN says to give each an aria-label. A single page footer is better left unlabeled, since MDN notes that role=”contentinfo” with aria-label=”Footer” may be announced redundantly as “contentinfo footer”.
The label earns its keep on repeated landmarks. A short ARIA label on the footer nav tells it apart from the header nav in a screen reader’s landmark list.
Target size and icon-only links
WCAG 2.2 SC 2.5.8 (Level AA) asks for a target area of at least 24 by 24 CSS pixels, or enough spacing that a 24 px circle centered on the target touches no other target.
Links inside a sentence are exempt. A list of footer links is not, and neither is a row of icons.
footer a { display: inline-block; min-width: 24px; min-height: 24px; padding: 4px; }<a href="https://example.com/linkedin" aria-label="Example Studio on LinkedIn">
<i class="fa-brands fa-linkedin" aria-hidden="true"></i>
</a>Deque’s axe-core target-size rule measures the largest unobscured area of each target and checks the 24 px spacing circle, so it catches icons that sit too close together.
Font Awesome’s documentation disagrees with its own older versions on icon fonts. As of October 2026, the current docs say web font icons are ignored by assistive technology by default and that SVG icons injected by a Kit get aria-hidden automatically, while the version 4 docs warn that screen readers can read CSS-generated icon content and tell authors to add aria-hidden themselves.
Putting aria-hidden on the icon and the name on the link works under both versions.
What Lighthouse reports
As of October 2026, Chrome for Developers lists “Links have a discernible name” and “Background and foreground colors have a sufficient contrast ratio” at a weight of 7 each in the accessibility score. Both are pass or fail per page, so if some links have names and others do not, the audit scores 0. One unnamed icon link in the footer is enough.
Tab through the footer once by hand as well. Overlap with a pinned footer shows up only at particular scroll positions.
Why does a footer float, cover content or overflow on mobile?
Match the symptom, not the property, because each failure comes from a different layer of the CSS.
| Symptom | Likely cause | Fix |
|---|---|---|
| Footer ends mid-page on short content | Wrapper min-height: 100% inside an html or body of auto height | Set html and body to height: 100%, or put min-height on the body |
| Last block of content hidden under the footer | Fixed footer left normal flow | Body padding-bottom plus html scroll-padding-bottom |
| Horizontal scrollbar, footer wider than the page | width: 100vw ignores classic scrollbars | width: 100% on the footer |
| Footer cut off at the bottom on phone load | 100vh taller than the visible area | min-height: 100dvh with a 100vh fallback |
| Footer jumps while scrolling on a phone | dvh updates as toolbars resize | min-height: 100svh |
A percentage min-height only works when the parent has a definite height. That is why MDN’s sticky footer recipe sets height: 100% on html and body before it sets min-height: 100% on the wrapper.
The 100vw row comes from web.dev. Viewport units assume scrollbars do not exist, so a 100vw element is slightly too wide wherever classic scrollbars take space.
Inspecting the wrapper in DevTools
In Chrome DevTools, a grid or flex badge appears next to any element with display: grid or display: flex. Toggling it draws an overlay with columns, rows, numbers and gaps for grid, or child positions for flex.
Firefox Developer Tools work a little differently. Clicking the grid icon beside display: grid in the Rules view toggles an overlay, and the Layout panel adds line numbers, area names, infinite line extension and a mini grid view.
Check the footer with near-empty content and with content taller than the viewport, then again at a narrow phone width.
Desktop Chrome has no dynamic toolbars, and web.dev notes that the large, small and dynamic viewports are identical there. The 100vh overshoot cannot show up in desktop emulation, so confirm it on a real phone.
Common Questions About CSS Footers
What should a website footer contain?
MDN’s footer reference says a footer typically holds author information, copyright data or links to related documents. In practice that means a copyright line, privacy and terms links, contact details and a short secondary nav.
Does Bootstrap include a sticky footer class?
Not as a single class. Its flex utilities build one, though, with d-flex, flex-column and min-vh-100 on the wrapper and mt-auto on the footer.
Ready-made Bootstrap footer layouts follow the same pattern.
Can Tailwind CSS keep a footer at the bottom of a short page?
Yes. Put min-h-screen, flex and flex-col on the wrapper, then add mt-auto to the footer or grow to main.
Finished Tailwind footer layouts use this structure. Note that min-h-screen maps to 100vh, so on phones swap it for the min-h-dvh utility, which maps to min-height: 100dvh.
Should a footer have a fixed height?
No, leave the height on auto and let padding size the footer. A hard-coded height clips or overlaps content when link text wraps on a narrow screen.
The exception is a fixed-position footer, where body padding needs a known height. Store that value in one custom property.
How do you center footer content in CSS?
Text-align: center handles inline text and links. For blocks, give the inner container display: flex with justify-content: center, or set a max-width and margin-inline: auto.
How do you style footer links with a hover state and a top border?
Add border-top to the footer rule, set the links to color: inherit, and write the hover and :focus-visible states in one selector.
Mouse and keyboard users then get the same cue.
How do you update the copyright year automatically?
Set the text from new Date().getFullYear() in a short script. Better still, render the year at build or server time, because text set by script is missing when JavaScript is off.
CSS footer examples
Mind-Blown Expandable Magic

Mustard Footer For the Win

Smashing Responsive Design

Insta-Worthy Footer

Whoa, Goey Much?

@itsrehanraihan’s Eyecandy

Less is More, Folks

Plain HTML, Big Impact

Slide ‘N Glide Footer

Footers Got Swag

Peekaboo Footer

Pure Sass & Class

Floating on Cloud Parallax

Why Basic When Responsive?

Content Shrink & Footer Pop

Double Trouble: Contact + Footer

Ride the Footer Wave

Smarty Two-Part Harmony

Dance of the Northern Lights

Ocean’s Serenity

Chit-Chat Corner

Rebel With A Cause

Clean Slate

On-the-Go Groove

Steve’s Footprint

Testing CSS Footers Before the Page Ships
Start with the layout, using a short page and then a page taller than the viewport. After that, open it on a real phone and watch the toolbars expand and retract. Tab through the footer last.
The keyboard check goes last because the footer height variable feeds both the body padding and the scroll-padding. Those values must be final before a tab-through proves anything.
The cost is rework. A late change, such as a taller footer on mobile, sends the pass back to the first step.
This order was verified in October 2026 against WCAG 2.2. A new WCAG version that changes the AA focus or target size criteria would change the final step.
Once the footer passes, the same checks apply to the matching CSS header at the top of the page.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026












