Fixed vs Sticky Position in CSS
What You Learn From This Page
- How fixed and sticky boxes participate differently in normal flow.
- How fixed containing blocks, scrollports, containing blocks, and inset thresholds work.
- Why ancestor transforms and overflow can change expected positioning behavior.
- How to build headers, sidebars, table headings, overlays, and mobile action bars.
- How persistent UI affects stacking, anchor navigation, zoom, mobile viewports, and safe areas.
- How to prevent fixed or sticky content from obscuring reading content and keyboard focus.
- How to diagnose sticky failures and choose the correct positioning scheme.
Introduction
position: fixed and position: sticky can both keep interface elements visible while content scrolls, but they solve different layout problems. A fixed box is taken out of normal flow and positioned against a fixed-positioning containing block, usually the viewport. A sticky box remains in normal flow and is shifted only when scrolling would carry it beyond a specified inset within its nearest scrollport.
This distinction affects much more than movement. Fixed content does not reserve its original space, so a header or footer can cover page content unless the layout compensates. Sticky content keeps its normal-flow space and remains constrained by its containing block, so it naturally stops when its section ends. The correct choice depends on whether the element belongs to the viewport or to the content being scrolled.
Persistent interface layers demand restraint. A compact sticky table heading can improve comprehension, while stacked sticky toolbars can leave almost no reading area at high zoom. This tutorial connects positioning mechanics with responsive design, keyboard focus, accessibility, and real component behavior so that a useful persistent control does not become an obstruction.
The Core Difference
Fixed positioning is a form of absolute positioning. The box is out of flow, its siblings behave as though it does not occupy normal space, and its location is calculated from a fixed-positioning containing block. In ordinary continuous media, that reference is usually the viewport, so the box stays at the same visible location during document scrolling.
Sticky positioning begins from relative-positioning behavior. The box remains in flow and reserves space. Its offsets are automatically adjusted against the nearest scrollport so it stays within a sticky view rectangle, but only as far as its containing block permits.
.viewport-action {
position: fixed;
inset-inline-end: 1rem;
inset-block-end: 1rem;
}
.section-navigation {
position: sticky;
inset-block-start: 1rem;
}
Quick Comparison
| Behavior | fixed | sticky |
|---|---|---|
| Normal-flow participation | Removed from flow | Remains in flow |
| Original space preserved | No | Yes |
| Primary reference | Usually viewport | Nearest scrollport plus containing block |
| Behavior before scrolling | Already fixed | Starts at normal-flow location |
| Needs non-auto inset to visibly pin an axis | Usually specified | Yes |
| Stops at content boundary | Not in the sticky sense | Yes |
| Creates a stacking context | Yes | Yes |
| Typical uses | Backdrops, floating actions, viewport bars | Section headings, tables, local navigation |
Understanding position: fixed
A fixed-positioned box is out of normal flow. It does not contribute to the normal size or position of its siblings and ancestors. It is sized and positioned using its fixed-positioning containing block, with inset properties such as top, bottom, and their logical equivalents determining its location.
.site-header {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
}
When the viewport is the containing block, scrolling moves the document behind the header while the header remains attached to the same viewport edge. That persistence is valuable only if the header truly needs to stay available from every scroll position.
Fixed Content Does Not Reserve Space
Because a fixed box is out of flow, the next in-flow element does not begin after it. A 4rem fixed header can cover the first 4rem of the main content. A bottom action bar can cover the final form controls or footer links.
:root {
--header-size: 4rem;
}
.site-header {
position: fixed;
inset-block-start: 0;
block-size: var(--header-size);
}
main {
padding-block-start: var(--header-size);
}
This compensation works when the header size is genuinely stable. If labels wrap or controls grow, a sticky in-flow header or a layout that measures the real size may be safer than duplicating a hard-coded height.
The Fixed Containing Block
The practical rule says fixed elements attach to the viewport, but CSS allows ancestors to establish a fixed-positioning containing block. A transform, perspective, certain containment modes, content visibility, or a relevant will-change value can make a fixed descendant behave as though it is attached to that ancestor.
.animated-shell {
transform: translateZ(0);
}
.animated-shell .fixed-control {
position: fixed; /* May reference the transformed shell. */
}
This is a frequent debugging surprise in animated application shells. Inspect containing-block indicators and ancestor computed styles before assuming the browser failed to keep the element viewport-fixed.
Fixed Full-Viewport Overlays
A full-screen backdrop is an appropriate fixed-position use because the surface belongs to the active viewport rather than to a particular article section. Setting every inset to zero fills the fixed containing block.
.backdrop {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 60%);
}
A complete modal requires more than positioning: it needs a semantic dialog, a name, focus placement, keyboard dismissal where appropriate, background inertness, and focus restoration. Native <dialog> and its top-layer behavior can handle important parts of that contract more reliably than a manually positioned div.
Fixed Headers
A fixed header remains attached from the first pixel of scrolling. This can suit an application toolbar that must always expose critical controls. It also permanently consumes viewport area and requires content offset, stacking, anchor-target handling, and responsive behavior.
.app-header {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: 20;
}
For ordinary editorial pages, sticky is often simpler because it remains in flow. Choose fixed only when immediate and uninterrupted viewport attachment is part of the requirement.
Fixed Bottom Bars
Mobile navigation, checkout actions, media controls, and consent notices are often placed at the viewport bottom. Since the bar overlays normal content, reserve enough block-end space for the final content and include any device safe-area inset.
.mobile-actions {
position: fixed;
inset-inline: 0;
inset-block-end: 0;
padding-block-end: env(safe-area-inset-bottom);
}
main {
padding-block-end: calc(4rem + env(safe-area-inset-bottom));
}
The bar should remain compact, dismissible when appropriate, and fully usable when the on-screen keyboard reduces visible space.
Fixed Floating Actions
A help launcher or back-to-top button can use fixed positioning to remain near a viewport corner. Logical insets adapt the location to writing direction, while minimum target size and a visible focus state support keyboard and touch users.
.help-button {
position: fixed;
inset-inline-end: 1rem;
inset-block-end: calc(1rem + env(safe-area-inset-bottom));
min-inline-size: 2.75rem;
min-block-size: 2.75rem;
}
Check whether the action covers page controls, cookie banners, chat windows, or browser interface at narrow sizes. Multiple independent fixed widgets frequently compete for the same corner.
Fixed Position in Paged Media
In paged media, a fixed box can be positioned relative to the page area and repeated on pages. That may be useful for a print header, but a screen-oriented navigation bar should usually not repeat through a printed tutorial.
@media print {
.screen-toolbar {
position: static;
display: none;
}
}
Inspect print preview and generated PDF output. Screen persistence and print repetition are different requirements even when they share the same position value.
Understanding position: sticky
A sticky box remains in its normal formatting context and preserves its layout space. It behaves like a relative box until scrolling would move it outside the sticky view rectangle defined by non-auto insets from the nearest scrollport. The browser then visually shifts it inward while keeping it within its containing block.
.section-title {
position: sticky;
inset-block-start: 0;
}
This produces three phases: ordinary flow before the threshold, constrained movement while stuck, and release when the containing block boundary prevents further sticking.
Sticky Needs an Inset Threshold
If both inset properties in an axis are auto, sticky positioning does not visibly constrain that axis. A top-sticking header normally needs top: 0 or inset-block-start: 0. The inset defines the edge of the sticky view rectangle, not merely decorative spacing.
.table-of-contents {
position: sticky;
inset-block-start: 1rem;
}
The example scrolls normally until its block-start edge would move closer than 1rem to the nearest scrollport edge. It is then shifted to maintain that inset as far as the containing block allows.
The Nearest Scrollport
Sticky offsets are calculated against the nearest scrollport. That may be the page, but it can also be an ancestor with scrolling overflow. A sticky table header inside a fixed-height overflow: auto wrapper sticks within that wrapper rather than to the browser viewport.
.table-wrapper {
max-block-size: 30rem;
overflow: auto;
}
.table-wrapper th {
position: sticky;
inset-block-start: 0;
}
Identify the element that actually scrolls. Watching the wrong scrollbar leads to the mistaken conclusion that sticky is broken.
Sticky and the Containing Block Boundary
The nearest scrollport determines the sticky view rectangle, but the sticky position is also limited so the box remains within its containing block. A sidebar inside an article section can remain near the viewport top while that section scrolls, then move away when the section ends.
This boundary behavior is the defining reason to choose sticky for local navigation, grouped headings, and section-specific tools. Fixed content has no equivalent automatic stop at the parent section's end.
Sticky Preserves Flow Space
Since sticky positioning begins from relative positioning, the box retains its original place in layout. When it shifts to stay visible, following content does not suddenly jump upward to fill the original location. This makes sticky headers easier to integrate into document flow than fixed headers.
The shifted sticky box can still overlap content passing behind it. Give it an appropriate opaque or translucent background, border, shadow, and stacking level so text does not become unreadable through the header.
Sticky Headers
A site or section header can scroll naturally until it reaches the viewport top and then remain visible. Unlike fixed positioning, its original height already participates in layout, so the first content is not automatically covered.
.site-header {
position: sticky;
inset-block-start: 0;
z-index: 10;
background: white;
}
Sticky remains persistent after reaching its threshold, so it still reduces reading space. Consider a compact state after scrolling or disable persistence at constrained heights if the header becomes disproportionately large.
Sticky Sidebars
Documentation navigation, filters, and order summaries often belong to a content region but benefit from staying visible while that region scrolls. Sticky positioning expresses that relationship directly.
.docs-layout {
display: grid;
grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
gap: 2rem;
}
.docs-navigation {
position: sticky;
inset-block-start: 1rem;
align-self: start;
}
At narrow widths, place the navigation in normal flow or behind an accessible disclosure instead of letting it consume most of the screen.
Sticky Table Headers
Sticky column headings help users retain context in long data tables. Apply positioning to the header cells when needed, provide a background so body rows do not show through, and use a small z-index to keep headings above scrolling cells.
.data-table th {
position: sticky;
inset-block-start: 0;
z-index: 1;
background: #fff;
}
Preserve native table semantics and test horizontal scrolling, merged cells, focusable content, borders, and zoom. Positioning should enhance the table without replacing its structural markup.
Sticky Section Headings
Grouped lists can pin the current section label while its entries pass underneath. Each heading remains constrained to its group, so the next group's heading naturally replaces it. This is useful for contacts, schedules, and categorized reference lists.
Multiple sticky headings are calculated independently and can overlap if they share the same inset. If several layers must stack, assign cumulative insets based on real sizes or redesign the hierarchy so only one persistent heading occupies that edge.
Top Sticky vs Bottom Sticky
Top sticky behavior is common because the element naturally approaches the top threshold while scrolling forward. A bottom-sticky element needs a layout situation in which its normal position can approach the block-end threshold; simply writing bottom: 0 does not guarantee the expected footer-like effect.
Place the element meaningfully in flow and test both scroll directions. If an action must remain at the viewport bottom regardless of content position, fixed may express the requirement more directly, provided covered content is handled.
Sticky and overflow
Ancestor overflow is one of the first things to inspect when sticky behavior differs from expectation. Values such as auto, scroll, and hidden can establish a scrolling mechanism or scroll container that changes the nearest scrollport. The sticky box may then reference that ancestor instead of the page.
overflow: clip clips overflow without creating a scroll container, unlike hidden, which can still permit programmatic scrolling. That distinction can matter when clipping is needed but a new sticky reference is not. Confirm browser support and test the complete overflow behavior before changing a production container.
Sticky Height and Available Range
A sticky box needs useful space in which to move. If it is as tall as its containing block or consumes most of the scrollport, its constrained range may be tiny and the effect can look absent. A parent that ends immediately after the sticky item also provides no meaningful sticking distance.
Keep sticky sidebars shorter than the available visible region or give their internal content an intentional scrolling strategy. Avoid nested scroll areas unless the interaction clearly benefits from them.
Sticky in Flexbox and Grid
Flex and Grid alignment can stretch an item across the available block size, leaving little room for sticky movement. Setting align-self: start commonly lets a sidebar use its natural height and reveals the intended scroll range.
.layout {
display: grid;
grid-template-columns: 16rem minmax(0, 1fr);
}
.filters {
position: sticky;
inset-block-start: 1rem;
align-self: start;
}
Do not add alignment fixes blindly. Inspect the item's used height, track size, containing block, and actual scroll container to understand why movement is constrained.
Stacking Contexts and z-index
Fixed and sticky positioned boxes form stacking contexts. Their descendants are painted within those local contexts, and z-index values compare according to the surrounding stacking hierarchy. A huge z-index cannot escape an ancestor context and outrank every unrelated element.
:root {
--layer-sticky: 20;
--layer-overlay: 100;
}
.sticky-header { z-index: var(--layer-sticky); }
.modal-backdrop { z-index: var(--layer-overlay); }
Use a documented layer scale and inspect transforms, opacity, isolation, containment, and positioned ancestors when a persistent element appears beneath another layer.
Anchor Links and Scrolling Targets
A fixed or stuck header can cover a heading reached through a fragment link. Use scroll padding on the scrolling container to reserve a general landing region, or use scroll margin on specific targets.
html {
scroll-padding-block-start: 5rem;
}
.article-heading {
scroll-margin-block-start: 5rem;
}
The reserved distance should match the actual persistent layer at the relevant breakpoint. Test direct URL fragments, table-of-contents links, browser find, focus movement, and programmatic scrolling.
Mobile Viewports and Safe Areas
Mobile browser chrome expands and contracts, the on-screen keyboard changes visible space, orientation changes dimensions, and devices can have notches or home indicators. A fixed interface designed against a desktop rectangle can become difficult to reach or can cover content on mobile.
Use safe-area environment variables where appropriate, avoid unnecessary fixed heights, and test real devices or realistic emulation. Dynamic viewport units can help some full-height surfaces, but they do not remove the need to handle keyboards, content overflow, and focus visibility.
Accessibility and Focus Obscuring
Persistent content must not completely obscure a component that receives keyboard focus. Sticky headers, fixed footers, chat panels, and consent notices are common causes because focus can move behind them while the layer remains above the page.
Reserve layout or scroll padding, keep persistent layers compact, make user-opened panels dismissible without advancing focus, and test Tab and Shift+Tab through the entire page. The focus indicator should remain visible as well as the focused component itself.
Zoom and Reflow
At high zoom, a fixed header and bottom bar can consume most of the visible height. A sticky sidebar can occupy most of the width after the layout collapses. Content may technically reflow while still becoming unreadable because persistent layers leave too little viewport area.
At narrow or zoomed layouts, consider returning toolbars and sidebars to static positioning or allowing users to collapse them. Test around a 320 CSS pixel-wide equivalent and with enlarged text, long translations, landscape orientation, and reduced viewport height.
Fixed and Sticky Reading Order
Positioning changes where a box is painted, not where it appears in the DOM. Screen-reader reading order and keyboard focus generally continue to follow document structure. A fixed action visually at the bottom-right can still occur early or late in the focus sequence according to its source location.
Place controls in a logical semantic order and ensure visual persistence does not suggest a contradictory sequence. Do not use positive tabindex values to imitate visual coordinates.
Performance and Scrolling
Persistent elements can require repainting or compositing while scrolling, especially when they use large shadows, filters, transparency, backdrop filters, or complex descendants. Sticky positioning also requires the browser to update offsets as the scroll position changes.
Modern browsers optimize common patterns, so do not add transforms or will-change reflexively. Excessive promoted layers consume memory and can create containing-block or stacking surprises. Profile real scrolling on representative devices before applying performance workarounds.
Multiple Sticky Layers
Several sticky boxes in the same scroll container are positioned independently and can overlap when they use the same inset. A site header, section toolbar, and table heading all set to top: 0 will compete for the same edge instead of automatically stacking beneath one another.
If every layer is necessary, define an intentional hierarchy and cumulative inset values based on stable component sizes. Keep backgrounds opaque enough for readability and document the stacking order. When those heights vary with wrapping or localization, simplify the design rather than duplicating fragile calculated offsets.
Persistent Component State
A fixed or sticky component can change state while it remains visible. A header may become compact, a bottom player may expand, or a filter summary may replace the full sidebar. State transitions must preserve an understandable focus location and avoid sudden content coverage.
User-opened persistent panels need a discoverable close control and should remember an intentional preference only when that behavior is expected. If a panel becomes unavailable at a responsive breakpoint, close it coherently, update expanded states, and return focus to a visible logical control. Positioning does not manage these interaction responsibilities automatically.
Testing Strategy
Test from the top, middle, and end of each relevant scroll container. Scroll with a wheel, touch, keyboard, Page Up and Page Down, and programmatic fragment navigation. Confirm when sticky behavior begins, where it stops, and whether fixed controls remain reachable while browser chrome changes.
Navigate every focusable control forward and backward. Verify that headers, footers, notices, and floating widgets never fully hide the focused component. Open the on-screen keyboard near bottom controls, rotate a mobile viewport, zoom substantially, enlarge text, and test long translated labels.
Finally, disable transforms and overflow rules during debugging, inspect print preview, and test reduced-height viewports as well as narrow widths. A component that works on a wide desktop can still fail when viewport height is constrained, which is especially relevant for stacked headers and bottom action bars.
Fixed vs Sticky vs Absolute
| Behavior | absolute | fixed | sticky |
|---|---|---|---|
| Normal flow | No | No | Yes |
| Primary reference | Absolute containing block | Fixed containing block | Nearest scrollport |
| Moves with document scrolling | Normally yes | Normally no | Until constrained |
| Limited by content container | Through containing block geometry | Not like sticky | Yes |
| Best suited to | Local layers | Viewport layers | Scroll-aware content |
Common Mistakes
A sticky element without a non-auto inset in the intended axis has no visible threshold. A sticky element inside an unexpected overflow ancestor may reference the wrong scrollport. A stretched or oversized item may have no useful movement range. Inspect all three before adding browser-specific workarounds.
A fixed header or footer without reserved content space covers the page. Hard-coded compensation breaks when the persistent layer wraps or changes at a breakpoint. Prefer a stable shared size, an in-flow sticky design, or a layout that responds to the actual component size.
Other common failures include stacking every toolbar, assigning arbitrary z-index values, ignoring safe areas, and leaving fixed widgets active in print. Persistent positioning should serve a clear task and should be removed when it consumes more usable space than it saves.
Debugging Sticky Positioning
Confirm the computed position is sticky and that at least one inset in the intended axis is non-auto. Identify the nearest scroll container and watch which element actually scrolls. Inspect every ancestor's overflow values, including differences between hidden and clip.
Measure the sticky box, its containing block, and the scrollport. Check whether Flexbox or Grid stretching makes the item too tall, whether the parent ends too soon, and whether another sticky layer overlaps it. Temporarily remove alignment, overflow, and height rules one at a time.
Debugging Fixed Positioning
Confirm whether the viewport or an ancestor forms the fixed containing block. Search ancestors for transform, perspective, filter-related behavior, containment, content visibility, and will-change. Inspect inset values, sizing constraints, safe-area calculations, and viewport changes when the keyboard opens.
For overlap problems, inspect stacking contexts rather than only the element's z-index. For covered content, tab through the page in both directions and test fragment links. At narrow sizes, verify that users can dismiss or collapse persistent surfaces without losing their place.
Decision Guide
Choose fixed when the element belongs to the viewport and must remain at the same viewport location regardless of the content section. Full-screen backdrops, genuinely global mobile actions, and carefully designed floating controls are common examples.
Choose sticky when the element belongs to document content, should begin in normal flow, and should remain visible only while its containing region is relevant. Table headings, section labels, documentation navigation, and product filters fit this model.
Choose neither when ordinary flow, Grid, or Flexbox already solves the layout. Persistent placement introduces overlap, stacking, focus, and small-screen responsibilities that should be accepted only when the user benefit is clear.
Common Interview Questions
What is the main difference between fixed and sticky? Fixed is out of flow and usually attached to the viewport; sticky remains in flow and is shifted after reaching an inset threshold within its scroll context.
Does fixed positioning reserve layout space? No. Content can move underneath a fixed box unless the design reserves space separately.
Does sticky positioning preserve its space? Yes. It begins from relative positioning and remains part of normal flow.
What does sticky need to show visible sticking? At least one non-auto inset in the axis being constrained, such as top: 0.
What controls a sticky element? Its nearest scrollport defines the sticky view rectangle, while its containing block limits how far it can remain shifted.
Why can ancestor overflow break an expected page-level sticky header? The overflow ancestor can become the nearest scroll container, changing the scrollport the sticky box references.
Is fixed always relative to the viewport? No. Certain ancestor properties, including transforms and containment, can establish a different fixed containing block.
Why use align-self start on a sticky sidebar? It prevents Flexbox or Grid stretching from consuming the movement range when natural-height alignment is required.
How can a persistent header avoid covering fragment targets? Use appropriate scroll padding on the scroller or scroll margin on the targets.
Which is better for a documentation table of contents? Sticky is usually better because it remains tied to the documentation region and stops at its boundary.
Practical Review Checklist
- Use fixed only for controls that genuinely belong to the viewport.
- Use sticky for scroll-aware content that remains part of document flow.
- Specify a non-auto inset for the sticky axis.
- Identify the real scroll container and containing block.
- Inspect ancestor overflow when sticky behaves unexpectedly.
- Inspect transforms and containment when fixed stops following the viewport.
- Reserve space for fixed headers, footers, and action bars.
- Use logical insets and account for device safe areas.
- Keep persistent UI compact at narrow widths and high zoom.
- Prevent sticky or fixed layers from fully obscuring keyboard focus.
- Use scroll padding or scroll margin for covered targets.
- Control stacking with a documented layer scale.
- Test mobile keyboards, orientation, long text, reflow, and print.
- Return persistent elements to normal flow when the available space is too small.
Key Takeaway
position: fixed removes a box from normal flow and positions it against a fixed containing block, usually keeping it at one viewport location throughout scrolling. position: sticky preserves normal-flow space, follows the document until an inset threshold is reached, remains constrained by the nearest scrollport, and stops at its containing block boundary.
Use fixed for true viewport-level layers and sticky for content-level elements that should become temporarily pinned. In both cases, account for stacking, covered content, anchor targets, focus visibility, mobile viewport changes, zoom, and reflow. Persistent positioning is successful only when it keeps useful controls available without taking the page away from the user.