Display & Positioning
Understand how CSS determines where elements appear on the page — from normal document flow through every positioning scheme to z-index stacking contexts and overflow control.
Normal Flow
Before any positioning is applied, the browser lays out elements according to normal flow. There are two key behaviors:
- Block-level elements (
div,p,h1-h6,section, etc.) stack vertically, each taking the full width available. - Inline elements (
span,a,strong,em, etc.) flow horizontally within a line, wrapping when the line is full.
/* Block elements (default display: block) */ div, p, h1, section, article, header, footer, main, nav, ul, ol /* Inline elements (default display: inline) */ span, a, strong, em, code, img, br, input
display: block
A block-level element takes up the full width of its parent, starts on a new line, and respects all box-model properties (width, height, margin, padding).
.block-demo { display: block; width: 200px; height: 60px; margin: 10px 0; padding: 10px; background: #646cff; }
Even with a set width, each block starts on a new line.
display: inline
Inline elements flow within text. They do not start on a new line, and they ignore width, height, and vertical margin/padding.
.inline-demo { display: inline; width: 200px; /* IGNORED */ height: 60px; /* IGNORED */ margin-top: 20px; /* IGNORED */ padding: 5px 10px; /* horizontal works, vertical doesn't push */ }
Width and height are ignored. Vertical margin is ignored. Only horizontal padding/margin works.
display: inline-block
The best of both worlds: flows inline like text, but respects width, height, and all box-model properties like a block.
.inline-block-demo { display: inline-block; width: 120px; /* WORKS */ height: 60px; /* WORKS */ margin: 10px; /* ALL directions WORK */ padding: 10px; /* ALL directions WORK */ vertical-align: top; }
display: block
display: inline (width/height ignored)
display: inline-block (best of both)
display: none vs visibility: hidden
Both hide elements, but they behave very differently in terms of layout impact.
/* Completely removed from layout */ .gone { display: none; } /* Hidden but still takes up space */ .invisible { visibility: hidden; }
display: none removes the element from the document flow entirely (no space, no events). visibility: hidden hides it visually but it still occupies its space in the layout. Screen readers also skip display: none elements.
position: static
The default positioning. The element follows normal flow. top, right, bottom, left, and z-index have no effect.
.static { position: static; /* default, rarely written explicitly */ top: 50px; /* IGNORED */ left: 100px; /* IGNORED */ }
position: relative
The element stays in normal flow (its original space is preserved), but you can offset it with top/right/bottom/left relative to its original position. It also establishes a containing block for absolutely positioned children.
.relative { position: relative; top: 20px; /* moves DOWN 20px from original spot */ left: 30px; /* moves RIGHT 30px from original spot */ }
The original space is preserved (gap remains). The box is shifted visually from its original position.
position: absolute
The element is removed from normal flow (takes up no space). It positions itself relative to its nearest positioned ancestor (one with position: relative/absolute/fixed/sticky). If none exists, it positions relative to the initial containing block (the viewport).
/* The classic pattern: absolute inside relative */ .parent { position: relative; /* establishes containing block */ } .child { position: absolute; top: 10px; right: 10px; }
If the parent doesn't have position: relative (or another positioning), the absolute child will position itself relative to the viewport or the nearest positioned ancestor further up the tree. Always set position: relative on the intended parent container.
position: fixed
The element is removed from normal flow and positioned relative to the viewport. It stays in place even when the page is scrolled. Commonly used for sticky headers, floating buttons, and modals.
/* Fixed header */ .navbar { position: fixed; top: 0; left: 0; right: 0; z-index: 1000; } /* Floating action button */ .fab { position: fixed; bottom: 20px; right: 20px; }
In a real page, the blue bar and the green button would stay fixed on screen while you scroll.
If any ancestor has a transform, perspective, or filter property, it creates a new containing block for fixed elements. The element will no longer be fixed relative to the viewport, but to that ancestor instead.
position: sticky
A hybrid of relative and fixed. The element scrolls with the page normally until it hits a specified offset (e.g., top: 0), then "sticks" in place until its parent scrolls out of view.
.sticky-header { position: sticky; top: 0; background: #0a0a1a; z-index: 10; } /* Sticky sidebar */ .sidebar { position: sticky; top: 80px; /* sticks 80px from top */ align-self: start; }
Scroll down inside this container to see sticky headers in action.
Item A1 — Lorem ipsum dolor sit amet
Item A2 — consectetur adipiscing elit
Item A3 — sed do eiusmod tempor
Item A4 — incididunt ut labore
Item B1 — Ut enim ad minim veniam
Item B2 — quis nostrud exercitation
Item B3 — ullamco laboris nisi
Item B4 — ut aliquip ex ea commodo
Item C1 — Duis aute irure dolor
Item C2 — in reprehenderit in voluptate
Item C3 — velit esse cillum dolore
Item C4 — eu fugiat nulla pariatur
Scroll inside the box above. Each section header sticks at the top, then gets pushed away by the next one.
For sticky to work: (1) you must set at least one offset (top, bottom, etc.), (2) the parent must have enough scrollable content, and (3) no ancestor can have overflow: hidden or overflow: auto (unless it is the scroll container itself).
top / right / bottom / left / inset
These offset properties only work on positioned elements (not static). The inset shorthand sets all four at once.
/* Individual properties */ .box { position: absolute; top: 10px; right: 20px; bottom: 10px; left: 20px; } /* inset shorthand (same as above) */ .box { position: absolute; inset: 10px 20px; } /* Full-bleed overlay */ .overlay { position: absolute; inset: 0; /* shorthand for top:0 right:0 bottom:0 left:0 */ }
z-index & Stacking Contexts
z-index controls the stacking order of positioned elements. Higher values appear in front. It only works on elements with a position value other than static.
.behind { position: relative; z-index: 1; } .middle { position: relative; z-index: 10; } .infront { position: relative; z-index: 100; } /* Things that create a new stacking context: */ /* - position + z-index (not auto) */ /* - opacity < 1 */ /* - transform, filter, perspective */ /* - isolation: isolate */
Stacking Context
A stacking context is like a self-contained z-index universe. Children's z-index values only compete against siblings within the same stacking context, never against elements in a parent or sibling context.
/* Parent creates a stacking context with z-index: 1 */ .parent { position: relative; z-index: 1; } /* This child has z-index: 9999, but it can NEVER appear above a sibling of .parent with z-index: 2 because parent's context caps it at 1 */ .child { position: absolute; z-index: 9999; }
Setting z-index: 999 on a position: static element does absolutely nothing. The element must have position: relative, absolute, fixed, or sticky for z-index to take effect.
overflow
Controls what happens when content is larger than its container.
.a { overflow: visible; } /* default: content spills out */ .b { overflow: hidden; } /* clips content, no scrollbar */ .c { overflow: scroll; } /* always shows scrollbar */ .d { overflow: auto; } /* scrollbar only when needed */ /* Per-axis control */ .e { overflow-x: auto; overflow-y: hidden; }
Centering Techniques Overview
Centering elements is one of the most common CSS tasks. Here is a summary of the most reliable methods.
Horizontal Centering
/* Block element with known width */ .center-block { width: 300px; margin: 0 auto; } /* Inline/inline-block content */ .center-text { text-align: center; }
Vertical + Horizontal Centering
/* Method 1: Flexbox (most common) */ .flex-center { display: flex; justify-content: center; align-items: center; } /* Method 2: Grid (simplest) */ .grid-center { display: grid; place-items: center; } /* Method 3: Absolute + Transform */ .abs-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Method 4: Absolute + inset + margin */ .inset-center { position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content; }
In modern CSS, display: grid; place-items: center; is the simplest way to center anything both horizontally and vertically. It works with any content size and requires just two lines of CSS.
Common Gotchas
An absolutely positioned element is completely removed from the document flow. Surrounding elements will not know it exists, potentially causing overlaps. Always account for the space the element should have occupied.
position: sticky does nothing if: (1) no offset is set, (2) the parent has overflow: hidden, or (3) the parent doesn't have enough content to scroll. All three conditions must be met for sticky to activate.
inline and inline-block elements respect whitespace in your HTML. Two <div> elements on separate lines will have a small gap between them. Fix with font-size: 0 on the parent, or use Flexbox instead.
Avoid escalating z-index values (100, 1000, 99999). Instead, use a consistent scale defined in CSS custom properties: --z-dropdown: 100; --z-modal: 200; --z-tooltip: 300;.
Setting overflow: hidden on a parent element will break position: sticky on its children. The sticky element needs an overflow-visible ancestor as its scrolling container.
If all children are position: absolute, the parent collapses to zero height because absolute elements are removed from the flow. Set an explicit height or min-height on the parent.
Pro Tips
Instead of writing top: 0; right: 0; bottom: 0; left: 0;, use the shorthand inset: 0. It's cleaner and supported in all modern browsers.
Use isolation: isolate on a parent element to create a new stacking context without changing any other visual property. This prevents z-index conflicts from leaking out.
position: absolute/fixed should be reserved for overlays, tooltips, modals, and decorative elements. For page layout, always use Flexbox or Grid — they handle responsive behavior and source order much better.
When using sticky headers with anchor links, add scroll-margin-top to your section targets so the sticky header doesn't overlap the anchor scroll target: section { scroll-margin-top: 80px; }.
Browser DevTools (Firefox in particular) have a stacking context inspector. Use it when z-index seems "broken" — the issue is almost always a stacking context boundary you didn't expect.
In modern CSS, you can use @container queries to make layout decisions based on the parent's size rather than the viewport. This reduces the need for complex positioned layouts in responsive designs.