Headers look simple until you start scrolling. Flexbox or grid arranges the logo, the links and whatever actions sit beside them, and position: sticky or fixed decides whether the bar follows the reader down the page. A media query or two handles what happens on a phone.
There's a rule you can fail now, too. W3C published WCAG 2.2 in October 2023, and criterion 2.4.11 (Level AA) fails any page where the header entirely hides the element holding keyboard focus. Sticky headers are the usual culprit.
Below are 18 CSS header examples you can try in place and copy: layouts, styles, headers that react to scrolling and mobile menus. Each one is written from scratch with the HTML and CSS shown, and the guide after the examples covers positioning, sticky bugs, responsive menus and accessibility.
The default pattern for product sites: logo on the left, links and a call to action on the right. margin-left: auto on the nav pushes everything after it to the right edge, and below 720px the links hide so only the logo and the button stay.
A three-column grid of 1fr auto 1fr keeps the logo in the exact center even when the two sides hold different amounts of text. It suits fashion, hospitality and editorial brands where the wordmark carries the page.
A thin top bar holds announcements and secondary links, and the main row keeps the primary navigation uncluttered. Each nav gets its own aria-label, so screen reader users can tell the two landmarks apart.
A newspaper-style stack: a small info line, a large serif wordmark sized with clamp(), and a section nav ruled with a double border. The nav wraps instead of collapsing, which suits publications with many short section names.
The header detaches from the page edges as a rounded bar with a soft shadow, a common look on SaaS and startup sites. Centered links use margin: 0 auto between the logo and the button. Add position: sticky and top: 16px to keep it floating while the page scrolls.
The current page is marked with aria-current="page", and the same attribute drives the green bar under the link, so the visual state and the accessible state cannot drift apart. Hover and focus grow the bar with scaleX.
App-style navigation where the links sit in a gray track and the current one is a raised white segment. On narrow screens the track moves to its own row and scrolls sideways instead of hiding links behind a menu.
Thick borders, a monospace face and full-height link cells that invert on hover. align-items: stretch makes every link fill the header height, so the hover block reaches edge to edge without fixed heights.
Search is the main action, so it takes the flexible middle of the row and grows up to 520px. The form carries role="search", and the focus ring sits on the whole field through :focus-within. On phones the search drops to a full-width second row.
A translucent bar blurs the photo behind it with backdrop-filter. Keep the -webkit- prefixed line for Safari before version 18, and check text contrast over the brightest part of the image, since the background shows through.
The header is absolutely positioned over the hero, so the image runs to the top of the page. A dark overlay on ::before keeps white links readable on any photo, and isolation: isolate keeps that overlay behind the content without z-index juggling.
Scroll inside each frame. These use CSS scroll-driven animations with animation-timeline: scroll(), so there is no scroll listener, and each one keeps a sensible static look where the feature is not supported.
A plain sticky header gains its shadow over the first 80px of scrolling. animation-range ties the keyframes to that distance, and the @supports block means browsers without scroll-driven animations simply keep the border line.
The header looks shorter after scrolling without its height changing: the whole bar slides up 18px, the logo scales down, and the inner items move back into the visible strip. Only transform, background and shadow animate, so nothing below it shifts.
A sticky header with a negative bottom margin equal to its height overlaps the hero, so it starts transparent on the photo. As the hero scrolls away, the animation fills in a white background and dark text. Without scroll-driven animation support, a dark tint keeps the links readable.
LongreadHow the grid shaped a century of print designShare
Scroll inside the frame to see the effect.
For long articles: the header shows the story title and a thin bar along its bottom edge that fills as the reader moves down the page. The bar is a pseudo-element scaled with scaleX, tied to the scroll position of the page.
<header class="hd-progress">
<a class="hd-progress__logo" href="#">Longread</a>
<span class="hd-progress__title">How the grid shaped a century of print design</span>
<a class="hd-progress__share" href="#">Share</a>
</header>
Responsive and mobile headers
Ways to collapse the navigation on small screens. The frames are phone-sized, so the mobile layout shows even on a desktop monitor.
A button element, not a checkbox, opens the menu, so it gets keyboard focus, Enter and Space for free. The script only flips aria-expanded and a class, and the icon turns into an X from that same attribute. A container query swaps to the inline link row once the header is 640px wide.
The native disclosure element opens and closes the menu with no script, and screen readers announce its expanded state on their own. It does not close when you tap elsewhere or press Escape, so it fits simple sites better than large navigation.
The same header markup appears twice here, in a wide container and a narrow one. It reacts to the width of its own container instead of the screen, which is what you want for a header component reused in a design system preview, a sidebar or a dashboard panel.
The strip across the top of a page that holds the logo and the main links, plus a search icon or a login button on plenty of sites, is a header element with CSS applied to it. The element only supplies the container. Layout, positioning and everything visual come from the stylesheet.
That container is plain HTML markup, which means a header with no CSS still renders. It’s an unstyled block with its children stacked in source order.
MDN describes the header element as a wrapper for introductory content, like a logo and navigation links, or a search form.
The logo is usually a linked image or text mark that points to the home page. Next to it sits a nav element holding the main links.
A lot of headers add a search field, a cart, an account link or a call-to-action button. On small screens there’s also a menu toggle, the button that opens the collapsed navigation.
Most of a header’s weight sits on website navigation, so every extra link costs attention. I’d cut before I’d add.
Header element versus navbar versus banner landmark
People use header, navbar and banner as if they were the same thing, and they aren’t.
The header element becomes a banner landmark only when its context is the body. Nested inside an article, aside, main, nav or section element, it drops to a generic role (MDN).
A navbar is a component rather than an element, and it normally lives inside the header.
A header isn’t a heading either. Headings (h1 to h6) go inside it or in the page content, and the header element cannot nest inside another header, a footer or an address element.
The GOV.UK Publishing Components guide says its layout header should have a banner role at the root of the component, so assistive technology can find the site header on every page.
How to Lay Out a CSS Header With Flexbox or Grid
Flexbox handles most headers. Put display: flex on the header, add justify-content and align-items, and the logo goes left, the navigation goes right and both share the same vertical center.
Grid takes over when the header has more than two zones.
gap sets link spacing without margin hacks. With three items in the header, margin-left: auto on the nav keeps it beside an action button, and flex-wrap on the nav list lets links drop to a second line before they overflow.
Grid for a multi-zone header
Grid wins when each part of the header needs its own fixed zone.
A three-column template of 1fr auto 1fr keeps the navigation centered no matter how wide the logo or the buttons are.
If the header is just a logo and one nav, and the source order already matches what people see, flexbox is enough.
Grid earns its extra lines when the nav has to sit dead center, or when the header needs a second row for a search bar or an announcement strip.
The grid inspector in Chrome and Firefox DevTools draws the columns and the names of grid areas on the page, which helps when the template uses named areas and you want to check the layout before writing more rules.
Setting header height with a custom property
A fixed height is predictable, right up until the navigation wraps or someone bumps their text size and the content gets clipped. min-height avoids that. The header grows when it has to and never drops below the floor the design sets.
Store the number once as a custom property, so every rule that depends on header height reads from one place.
The same variable can drive the content offset of a fixed header and the scroll offset for anchor links.
Change the number once and every dependent rule follows.
Static, Sticky, Fixed or Absolute: Which Position Fits a Header?
You probably want position: sticky instead of fixed · Kevin Powell
Sticky fits most headers. Choose fixed only when the header must leave the document flow on purpose, for example a transparent header layered over a full-screen hero.
The four values mostly differ on whether the header stays visible during scrolling and whether it keeps its space in the layout.
Value
Stays visible on scroll
Keeps space in flow
Typical failure
static or relative
No
Yes
Header scrolls out of view
sticky
Yes, after reaching its offset
Yes
Breaks under overflow ancestors
fixed
Yes, always
No
Content hides beneath it
absolute
No, moves with its container
No
Needs a positioned parent
A fixed header leaves the flow, so the first content block starts underneath it. Add padding-top to the body equal to the header height (the –header-height variable works here).
A fixed header is pinned to the viewport, with one documented exception. When an ancestor has a transform, perspective or filter value other than none, that ancestor becomes the containing block and the header stops tracking the screen (MDN).
Sticky behaves like relative until the header reaches its top offset, then holds that offset until its containing block ends.
As of October 2026, Can I Use lists full support from Chrome 91, Edge 91, Firefox 59 and Safari 13 (Safari 7.1 to 12.1 needs the -webkit- prefix), and no support in Internet Explorer 11. Earlier Chrome, Edge and Firefox releases already handle a sticky header, and their gaps were limited to table parts.
There’s usability evidence behind all this as well. A 2024 experiment by Murano and Kongshaug at Oslo Metropolitan University (published through SCITEPRESS) found higher performance and satisfaction with persistent headers, especially on slightly larger screens. That is the usability case behind sticky navigation on long pages.
Sticky or fixed: pros and cons
Sticky keeps its space in the flow, so the body needs no offset, and nothing jumps at the moment it sticks. It takes two declarations, position and top. The catches: it stops working when an ancestor sets overflow, and it stops at the end of its parent element.
Fixed layers over the content, which is what makes a transparent header on a hero image possible, and it ignores the height of its parent. You pay for that with a manual content offset, and the header loses its viewport reference under transformed ancestors.
My default is sticky. Switch to fixed when an ancestor’s overflow cannot be removed or when the header has to overlay the content below it.
Write the markup: a header element holding the logo link and a nav element with the links.
Apply the flex rules from the layout section: display flex, justify-content space-between and align-items center.
Add position: sticky and top: 0 to the header. A sticky element needs at least one inset value to know where to pin.
Give the header a solid or semi-opaque background color, so scrolling content does not show through the text.
Set a z-index higher than any positioned content below the header.
Scroll the page and inspect the header in Chrome DevTools. The computed position must read sticky, and the header must hold the top edge.
Bootstrap’s documented z-index scale puts dropdowns at 1000, sticky elements at 1020 and fixed elements at 1030, a usable reference when choosing a value.
A banner above the header changes the math. If the banner is also sticky, set top on the header to the banner height so the two stack instead of overlapping.
Bootstrap ships the pattern as the sticky-top helper class, applied to its Bootstrap navbar component.
Tailwind CSS reaches the same result with the sticky and top-0 utilities. Its documentation describes sticky as relative until a threshold is crossed, then fixed until the parent leaves the screen, so a Tailwind navbar needs only those two classes for the pinning itself.
For a single header, hand-written CSS is lighter than adding a framework. Frameworks pay off when the rest of the interface already uses them.
Why Is Position Sticky Not Working on a Header?
5 Reasons your CSS Sticky Position won’t work! · Design with Cracka
Sizzy’s troubleshooting guide (updated June 2026) lists a missing offset first and overflow on an ancestor second. The offset fix is the easy one: add top: 0 to the header itself.
Overflow takes more digging. Remove hidden, auto or scroll from every ancestor up to the body. Overflow hidden creates a scroll container even when nothing scrolls, so the header measures against that box and never reaches its offset. Overflow clip clips the same way without creating a scroll container (MDN).
Pair clip with visible or clip on the other axis, because a hidden or auto value on the second axis turns clip back into hidden. Can I Use shows clip supported from Chrome 90, Edge 90, Firefox 81 and Safari 16, so a header on Safari 15 or older still needs the overflow removed.
A short parent is the next suspect. Make the header a direct child of the body or of a wrapper as tall as the page, since sticky stops where its parent ends.
A header that sticks but disappears under content has a stacking problem. Opacity below 1, a transform other than none and a filter other than none each create a stacking context (MDN). Inside one, z-index values compete only within that context, so the header cannot rise above a layer that outranks its wrapper. Raise the z-index or move the header out of the stacking context.
In Chrome DevTools, walk up the ancestors in the Elements panel and check the computed overflow, opacity, transform and filter values of each.
When the offending ancestor cannot change, switch the header to position: fixed and add the content offset.
How Do You Stop a Sticky Header From Covering Anchors and Focused Elements?
Set scroll-padding-top on the root element to the header height. The browser then stops every anchor jump and every keyboard focus scroll that distance below the top edge, so nothing lands behind the header.
html {
scroll-padding-top: calc(var(--header-height) + 16px);
}
The 16px buffer matters because a focus ring is drawn outside the element box. Without it, the ring can still slip under the header edge.
Set scroll-padding-top once on the scrolling element (html on a normal page) and it applies to anchor links, focus changes and scrollIntoView calls. scroll-margin-top goes on an individual target instead, which helps when one section needs a bigger gap than the rest.
W3C’s understanding notes for the criterion name scroll padding as one way to keep a sticky header from hiding the focused item. Under WCAG 2.2 (W3C, October 2023), keeping focus clear of sticky content became a web accessibility success criterion rather than a courtesy.
Criterion 2.4.11, Focus Not Obscured (Minimum), sits at Level AA and says a focused component cannot be entirely hidden by author-created content such as a sticky header. The Enhanced version, 2.4.12, is Level AAA and goes further, since no part of the focused component can be hidden.
The classic failure goes like this. A keyboard user presses Tab, the page scrolls the next link into view, and the link lands behind the sticky bar together with its focus ring.
Write the small-screen layout first, then add a min-width query at the point where the navigation links stop fitting. The collapsed menu is the default, and the full link row appears at the breakpoint.
Breakpoints and nav collapse
Pick the breakpoint from the content, not from a device list. Widen the window until every link sits on one row without crowding, and use that width.
Because the base rules target small screens, a single block of media queries covers the whole header.
Size container queries have been Baseline newly available since February 2023 (MDN), and Baseline counts a feature as widely available after 30 months (web.dev). Container queries have cleared that mark.
They pay off when one header component renders in containers of different widths, such as a design-system preview or a sidebar layout. For a full-width site header, a media query gives the same result.
The checkbox hack works without JavaScript, but it exposes a checkbox to assistive technology instead of a menu button. A real button takes keyboard focus and responds to Enter and Space by default, and a few lines of JavaScript keep aria-expanded in sync with the open state.
I’d go with the button. The checkbox saves a short script and costs the semantics.
Whatever icon sits on it, a hamburger menu is still a button that shows and hides the nav.
Give the open panel a max-height and overflow-y: auto, so a long link list scrolls inside the panel instead of running past the screen edge.
Safe-area insets on notched screens
A header flush to the top edge can sit under a notch. Add viewport-fit=cover to the viewport meta tag first, or the insets stay at zero. Then pad the header with env(safe-area-inset-top), plus the left and right insets in landscape. Rectangular screens report zero, so the rule costs nothing on desktop (MDN).
Watch a sentinel element with an Intersection Observer, then toggle a class that changes visual properties only. A scroll listener runs on every scroll event, while the observer fires when the sentinel crosses the viewport edge (Chrome for Developers documents this sentinel technique for sticky elements).
A translucent header with a blur uses backdrop-filter. The blur comes from the CSS blur effect family of properties, and backdrop-filter is the one that blurs what sits behind an element.
It also creates a stacking context on the header (MDN), so the header’s z-index covers it and its children as one layer. Safari and iOS shipped the unprefixed backdrop-filter in version 18 (WebKit), so older Safari needs -webkit-backdrop-filter alongside it.
For a shrinking header, animate transform rather than height. Switching a header from static to fixed on scroll costs the same, because the header leaves the flow and the content below jumps up by its height.
Good Cumulative Layout Shift, one of the Core Web Vitals, is 0.1 or less at the 75th percentile of page loads, and each shift scores impact fraction times distance fraction (web.dev).
Spendesk’s engineering team traced a CLS flag on its marketing site to an animation of the top property and fixed it by switching to transform: translateY() (Spendesk engineering blog).
Native scroll-driven animations tie an effect to scroll position with animation-timeline: scroll() and no listener. They work in Chrome 115 and later and in Safari 26, but Firefox still keeps them behind an experimental preference in stable releases (MDN’s list of experimental features in Firefox), so wrap them in @supports and keep the class toggle as the fallback.
For hide on scroll down, a short script compares the current scroll position with the last one and toggles a class that applies translateY(-100%). The header keeps its space in the flow, so nothing shifts. More CSS scroll effects follow the same rule: transform and opacity move freely, layout properties do not.
How to Make a CSS Header Accessible
Web Accessibility: Skip Links · Jacob Martella Web Design
Start with a skip link as the first focusable element on the page. WCAG 2.4.1 Bypass Blocks (Level A) requires a way to bypass repeated blocks, and W3C’s technique G1 is a link at the top of the page that goes straight to the main content.
<a class="skip-link" href="#main-content">Skip to main content</a>
Hide it off-screen, not with display: none or visibility: hidden, which remove it from the tab order. The scroll-padding-top rule from the offset section also covers the skip link jump, so the main content does not land under a sticky header.
Two nav elements on one page need distinct accessible names. The W3C ARIA Authoring Practices disclosure example names its navigation landmark with an aria-label.
A header needs very little ARIA when native elements do the work. The toggle still needs aria-expanded for the open state, aria-controls pointing at the nav, and a text name or aria-label.
The Authoring Practices example skips the WAI-ARIA menu role, because site navigation does not need menu keyboard behavior. WebAIM’s 2026 analysis of one million home pages supports that caution: 22% of the ARIA menus it found introduced accessibility barriers.
The same analysis found pages with ARIA averaging 59.1 detected errors against 42 for pages without it. WebAIM notes that more complex pages explain part of that gap, so the number does not prove ARIA caused the errors.
Wrap slide and shrink effects in a prefers-reduced-motion query. W3C technique C39 uses that query to suppress motion, and WCAG 2.3.3 (Level AAA) asks that motion triggered by interaction can be disabled.
Keep a visible focus indicator on every header link and button. Check link text and the focus ring against the header background in its scrolled, blurred state, because color contrast depends on whatever content is scrolling underneath.
Test with the keyboard only. Tab from the top of the page and confirm that the skip link appears, every item shows focus and the toggle opens without a mouse.
CSS Header FAQ
Can a page have more than one header element?
Yes. Each article, section, aside or main element can carry its own header. Only the header whose context is the body counts as the page banner, and a header cannot sit inside another header, a footer or an address element.
A header is a block element, so it already spans its container. If a max-width wrapper sits around it, move the wrapper inside the header. The background then runs edge to edge while the content stays constrained with max-width and margin-inline: auto.
Set background-image with background-size: cover and background-position: center, and give the header a min-height so the image has room to show.
Layer a linear-gradient above the image in the same declaration when text sits on top, so link contrast stays readable on bright photos.
How do you add a search bar to a header?
Add a form with role=”search” and a labeled input as another flex item, then push it right with margin-left: auto. On small screens, collapse it behind an icon button that carries aria-expanded, the same way the menu toggle works.
Yes. Nest a list inside the nav item, position it absolutely against that item, and reveal it with :hover and :focus-within. Never set overflow: hidden on the header, because it clips the open panel. Touch and keyboard users need a disclosure button instead of hover.
Should header height use px or rem?
Use rem for min-height and padding, so the header grows when a user raises the browser font size. Keep px for fixed-size parts such as a logo image, and store the final value in the custom property that feeds scroll-padding-top.
The Smallest CSS Header Worth Shipping First
Ship a header element with a flex row, position: sticky, one custom property for the height and a skip link. Scroll effects, blur and animation can wait until that baseline passes a phone check and a keyboard-only check.
Order matters, because each step depends on the one before it. Baseline first (markup, flex layout, sticky offset), then the checks (keyboard tab-through, narrow phone view), then the effects, with a class toggle before any transform-only motion.
Sticky positioning spends vertical space on every screen, which is the price of keeping navigation within reach.
Everything above was checked in October 2026. Once Firefox ships scroll-driven animations in its stable release, the observer script can be dropped. A header with dropdown navigation is the natural next build, and dropdown menus in CSS cover it.