Skip to main content
    Courses/HTML & CSS/Advanced Positioning

    Lesson 33 โ€ข Advanced Track

    Advanced Positioning: sticky, fixed, absolute

    By the end you'll be able to pin badges, centre overlays, build sticky headers, and finally understand why z-index sometimes refuses to work.

    What You'll Learn

    • Tell relative, absolute, fixed and sticky apart
    • Use the containing block to control where absolute lands
    • Place elements precisely with top / right / bottom / left
    • Build sticky headers, centred overlays and tooltips
    • Layer elements correctly with z-index
    • Know what creates a new stacking context (and why z-index fails)

    Prerequisites: You should be comfortable with selectors and the box model first. If not, revisit CSS Basics & Selectors before continuing.

    ๐Ÿ’ก Real-World Analogy

    Static is a book on a shelf โ€” it sits exactly where the flow puts it. Relative is sliding that book a few centimetres while leaving its gap on the shelf. Absolute is a sticky note pinned inside an open drawer โ€” move the drawer (the parent) and the note moves with it. Fixed is a sticker on your monitor glass โ€” it never moves no matter what scrolls behind it. Sticky is a fridge magnet that slides down with the door until it hits the bottom rail and stays there.

    1. The five position values

    Every element has a position. The default is static โ€” it ignores top/right/bottom/left and sits where the normal flow puts it. The other four switch on placement powers. The single most important idea is the containing block: the box that an element's offsets are measured from.

    • absolute is measured from the nearest positioned ancestor (an ancestor whose position is relative, absolute, fixed or sticky). If there is none, it falls back to the page.
    • fixed is measured from the viewport โ€” the visible window โ€” so it ignores scrolling.
    • sticky is measured from its scroll container, toggling between relative and fixed at a threshold.

    In the worked example below, position: relative nudges box 2 down and right, but keeps its original gap in the flow โ€” that is the giveaway that relative elements still occupy their space.

    ๐Ÿ“‹ Quick Reference โ€” position values

    ValueMeasured fromIn flow?Scroll behaviour
    staticNormal flow (default)YesScrolls normally; ignores offsets
    relativeIts own normal positionYes (gap kept)Scrolls normally
    absoluteNearest positioned ancestorNoScrolls with that ancestor
    fixedViewportNoStays put while scrolling
    stickyScroll containerYes (until stuck)Locks at its threshold

    Worked example: position: relative nudges without leaving its slot

    Run it and watch box 2 move while boxes 1 and 3 stay put.

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; background: #f5f5f5; }
      .row { display: flex; gap: 16px; margin: 24px 0; }
      .box { width: 90px; height: 90px; background: #1976D2; color: white;
             display: flex; align-items: center; justify-content: center;
             border-radius: 8px; font-weight: 700; }
    
      /* position: relative keeps the box IN the normal flow, but lets you nudge
         it with top/left. The space it WOULD have taken is st
    ...

    2. The absolute + relative pattern

    Step 1: put position: relative on the parent. It barely changes anything visually, but it becomes the containing block.

    Step 2: put position: absolute on the child plus the offsets you want (top/right/bottom/left).

    The child is now placed against the parent's edges. This one pattern powers notification badges, close buttons, tooltips and dropdown menus.

    Worked example: badge + tooltip with absolute inside relative

    Three real components built from the same two-step pattern.

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      h2 { color: #1565C0; }
    
      /* THE PATTERN: parent is "relative" (the containing block),
         child is "absolute" and pinned to the parent's edges. */
      .relative-box {
        position: relative;   /* ๐Ÿ‘ˆ makes THIS the containing block */
        background: #FFF3E0; padding: 40px 20px; border-radius: 12px;
        margin: 20px 0; border: 2px dashed #FF9800;
      }
      .abs-badge {
        position: absolute;   /* me
    ...

    ๐ŸŽฏ Your Turn 1 โ€” centre an overlay

    Fill in the ___ blanks so the white card sits dead-centre of the blue stage, both horizontally and vertically. Use the classic absolute-centring trick: top: 50%; left: 50%; transform: translate(-50%, -50%).

    ๐ŸŽฏ Your Turn: perfectly centre the card

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; margin: 0; }
      /* ๐ŸŽฏ YOUR TURN โ€” perfectly centre the card inside .stage */
      .stage {
        position: relative;   /* containing block is ready for you */
        height: 280px; background: #1976D2; overflow: hidden;
      }
      .card {
        /* ๐Ÿ‘‰ 1) take the card out of flow so we can place it freely */
        position: ___;        /* ๐Ÿ‘‰ use the value measured from .stage */
        /* ๐Ÿ‘‰ 2) move its top-left corner to the centre of
    ...

    3. Fixed and sticky: persistent UI

    fixed glues an element to the viewport โ€” perfect for headers, floating action buttons and cookie bars that must stay visible.

    A sticky element flows normally until you scroll past its threshold (e.g. top: 56px), then it locks there until its parent scrolls away. Sticky is the modern way to build section headers and table headers without JavaScript.

    Worked example: a fixed header plus sticky section labels

    Scroll inside the demo to see fixed stay put and sticky lock at its threshold.

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; margin: 0; padding-bottom: 80px; }
    
      /* FIXED: glued to the viewport. Scrolling the page does NOT move it. */
      .fixed-header {
        position: fixed; top: 0; left: 0; right: 0;   /* stretch across the top */
        background: #1976D2; color: white; padding: 12px 24px;
        z-index: 100;   /* sit above page content */
        font-weight: 700; box-shadow: 0 2px 8px rgba(0,0,0,0.2);
      }
    
      /* A fixed header overlaps conten
    ...

    Sticky needs a threshold and a scroll container. You must give it at least one of top/right/bottom/left, and no ancestor may have overflow: hidden/scroll/auto โ€” that quietly clips sticky and it silently stops working.

    ๐ŸŽฏ Your Turn 2 โ€” pin a SALE badge

    Fill in the ___ blanks so the pink SALE badge sits in the product card's top-right corner. Remember the two steps: make the card relative, make the badge absolute.

    ๐ŸŽฏ Your Turn: pin the SALE badge to the corner

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 40px; background: #f5f5f5; }
      /* ๐ŸŽฏ YOUR TURN โ€” pin the SALE badge to the card's top-right corner */
      .product {
        /* ๐Ÿ‘‰ 1) make the card the containing block for the badge */
        position: ___;        /* ๐Ÿ‘‰ relative */
        width: 220px; background: white; border-radius: 12px;
        padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);
      }
      .badge {
        /* ๐Ÿ‘‰ 2) take the badge out of flow, measured fr
    ...

    4. z-index and stacking contexts

    When positioned elements overlap, z-index decides who is on top โ€” higher numbers win. But there is a catch that trips up nearly everyone: z-index is only compared within the same stacking context.

    A stacking context is a self-contained layer pile. A child's z-index โ€” even 9999 โ€” can never escape its parent's context. The cure for z-index wars is not bigger numbers; it is creating contexts deliberately, e.g. with isolation: isolate.

    A new stacking context is created by, among others: a positioned element with a z-index other than auto, opacity less than 1, any transform or filter, and isolation: isolate.

    Worked example: a z-index: 999 box that still loses

    Proof that z-index is scoped to its stacking context.

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      h2 { color: #1565C0; }
    
      .stack { position: relative; height: 180px; margin: 16px 0; }
      .layer { position: absolute; width: 180px; padding: 16px; border-radius: 12px;
               color: white; font-weight: 700; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
      /* Higher z-index paints LATER, so it lands on top. */
      .red   { background: #F44336; top: 0;   left: 0;   z-index: 1; }
      .green { backgroun
    ...

    When to reach for each

    • Sticky: table headers, section labels, sidebars that follow the scroll
    • Fixed: persistent navbars, floating action buttons, cookie banners, back-to-top buttons
    • Absolute: notification badges, tooltips, dropdown menus, close buttons, centred overlays
    • Isolation: wrap a component in isolation: isolate to stop its z-index leaking out

    ๐Ÿง— Mini-Challenge โ€” back-to-top button

    Now with the scaffolding removed. The starter has only a comment outline โ€” write the .back-to-top rule yourself so a circular blue button stays glued to the bottom-right corner while the page scrolls.

    ๐Ÿง— Mini-Challenge: style the fixed back-to-top button

    Try it Yourself ยป
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; margin: 0; }
      .page { padding: 24px; max-width: 700px; margin: 0 auto; }
      p { line-height: 1.8; color: #555; }
    
      /* ๐ŸŽฏ MINI-CHALLENGE: a circular "โ†‘" button fixed to the bottom-right corner.
         1. position: fixed so it ignores scrolling and sticks to the viewport
         2. bottom: 24px and right: 24px to sit in the corner with breathing room
         3. width/height 56px + border-radius: 50% for a circle
         4. z-
    ...

    Common Errors & Fixes

    • "My z-index does nothing / a smaller number wins." Either the element isn't positioned (z-index only works on relative/absolute/fixed/sticky), or a parent created a stacking context (opacity < 1, a transform, a filter, or isolation: isolate) that traps the child's z-index inside it. Fix: raise z-index on the parent context, or remove the property creating it.
    • "position: sticky just won't stick." Sticky needs a threshold and a scroll container. Fix: add at least one of top/right/bottom/left, ensure the parent is tall enough to scroll, and check that no ancestor has overflow: hidden/scroll/auto, which clips sticky.
    • "My absolute element jumped to the corner of the page." There was no positioned ancestor, so it measured from the page. Fix: add position: relative to the parent you want it placed inside.
    • "My fixed header covers the first paragraph." Fixed elements leave the flow, so content slides underneath. Fix: add padding-top (or margin-top) equal to the header's height on the body or content wrapper.
    • "My fixed element acts like absolute." A transform, filter or perspective on any ancestor re-bases fixed to that ancestor. This is the spec, not a bug. Fix: move the fixed element out of the transformed ancestor.

    Frequently Asked Questions

    What is the difference between position: relative, absolute, fixed and sticky?

    relative keeps the element in the normal flow but lets you nudge it with top/left and makes it a containing block for absolute children. absolute removes the element from the flow and positions it against the nearest positioned ancestor. fixed removes it from the flow and positions it against the viewport, so it ignores scrolling. sticky is a hybrid: the element behaves like relative until you scroll past its threshold (e.g. top: 0), then it behaves like fixed within its scroll container.

    Why isn't my z-index working?

    Two common reasons. First, z-index only applies to positioned elements (position is relative, absolute, fixed or sticky) โ€” on a static element it does nothing. Second, z-index is only compared within the same stacking context. If a parent created a new stacking context (via opacity below 1, a transform, a filter, or isolation: isolate), then a huge z-index on a child is still trapped inside that parent and cannot rise above elements outside it.

    Why is my position: sticky element not sticking?

    Sticky needs three things. It needs a threshold โ€” at least one of top, right, bottom or left โ€” or the browser has no line to stick to. It needs room to scroll within its parent. And no ancestor can have overflow set to hidden, scroll or auto, because that clips the sticky element to that ancestor. Also make sure the parent is taller than the sticky element, otherwise there is nothing to scroll past.

    Why did my absolute element jump to the corner of the page?

    absolute positions against the nearest ancestor that is itself positioned (relative, absolute, fixed or sticky). If no ancestor is positioned, it falls all the way back to the initial containing block โ€” effectively the page. The fix is to add position: relative to the parent you want it measured from.

    How do I perfectly centre an element with positioning?

    Set the parent to position: relative, then on the child use position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%). top/left 50% places the child's top-left corner at the centre, and the translate pulls it back by half of its own width and height so its centre lands on the centre. For ordinary layout, Flexbox (justify-content/align-items: center) is usually simpler.

    ๐ŸŽ‰ Lesson Complete

    • โœ… static ignores offsets; relative nudges but keeps its gap and becomes a containing block
    • โœ… absolute is measured from the nearest positioned ancestor โ€” give the parent position: relative
    • โœ… fixed is glued to the viewport; sticky locks at a threshold inside its scroll container
    • โœ… Centre anything with top:50%; left:50%; transform: translate(-50%,-50%)
    • โœ… z-index only works on positioned elements and only inside one stacking context
    • โœ… opacity < 1, transform, filter and isolation: isolate all create stacking contexts
    • โœ… Use positioning for overlays and badges โ€” use Flexbox/Grid for page layout

    Next up: turn boxes into custom silhouettes in Shapes & Clip-Path.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service