\n\n
\n \n
\n Sticky Header\n
\n\n \n
\n \n \n
\n
\n
Card 1
\n
\n \n \n \n \n \n \n
Row 1
\n
\n \n
\n
\n\n \n \n
\n \n
\n\n \n
\n \n
\n
\n
\n\n\n```\n\nScrolls snap to each card top, below header. Expand one—snaps gracefully to siblings.\n\n## Common Pitfalls and Testing Tips {#common-pitfalls-testing}\n\n- **No mobile snap?** Add `-webkit-overflow-scrolling: touch` arbitrary class.\n- **Calc precision:** Use devtools to measure—`100vh` includes bars sometimes.\n- **Flex gaps:** They add to total height; test with `gap-0`.\n- **Browser quirks:** Chrome/Safari solid; Firefox tweak `scroll-snap-type: y proximity`.\n- Test: Trackpad fling, mobile swipe. [Stack Overflow flex scroll](https://stackoverflow.com/questions/61759029/css-scroll-snap-not-snapping-on-to-sections) flags `flex-shrink` needs.\n\nTweak heights for your padding. Works on latest browsers (2025).\n\n## Sources {#sources}\n- https://css-tricks.com/practical-css-scroll-snapping/\n- https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap/Basic_concepts\n- https://andromedagalactic.com/blog/scroll-snap-sticky-header\n- https://tailwindcss.com/docs/scroll-snap-type\n- https://stackoverflow.com/questions/61759029/css-scroll-snap-not-snapping-on-to-sections\n- https://stackoverflow.com/questions/62483752/scrolling-when-contained-in-a-flex-box\n\n## Conclusion {#conclusion}\n\nNail CSS scroll snap with a height-bounded container, `scroll-pt-16` for your sticky header, `snap-start` on cards, and `snap-proximity` for expandables. This setup delivers smooth, top-aligned snaps that respect your layout—try the code and fling-scroll to feel it click. Scale it to more cards or dynamic heights with the same principles."},{"name":"How to Fix CSS/Tailwind Scroll Snapping with Sticky Header","step":[{"name":"Why CSS Scroll Snap Isn't Working","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":1},{"name":"Give Your Scroll Container an Explicit Height","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":2},{"name":"Offset Snap Points with Scroll Padding for Sticky Headers","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":3},{"name":"Use the Right Snap Alignment and Type on Cards","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":4},{"name":"Handle Expandable Cards Without Breaking Snaps","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":5},{"name":"Complete Working Code Example","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":6},{"name":"Common Pitfalls and Testing Tips","@type":"HowToStep","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","position":7}],"@type":"HowTo","@context":"https://schema.org","description":"Step-by-step guide to resolve CSS and Tailwind scroll snapping issues, especially with sticky headers and expandable cards, ensuring correct alignment and behavior.","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable"},"inLanguage":"en","dateCreated":"2025-12-26T15:51:40.734Z","datePublished":"2025-12-26T15:51:40.734Z","dateModified":"2025-12-26T15:51:40.734Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","url":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable"},{"@type":"CollectionPage","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable/#related-questions","name":"Fix CSS/Tailwind Scroll Snap with Sticky Header & Expandable Cards","description":"Resolve CSS/Tailwind scroll snapping issues for expandable cards with a sticky header. Learn why it fails and implement `scroll-padding-top` for perfect alignment.","url":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable","inLanguage":"en","mainEntity":{"@type":"ItemList","@id":"https://neuroanswers.net/c/web/q/fix-css-tailwind-scroll-snap-sticky-header-expandable/#related-questions","itemListElement":[{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/smooth-flexbox-animation-scrolling-flip-technique","name":"Smooth Flexbox Animation During Scrolling: FLIP Technique","position":1,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/smooth-flexbox-animation-scrolling-flip-technique","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/smooth-flexbox-animation-scrolling-flip-technique"},"inLanguage":"en","dateCreated":"2025-12-26T11:40:10.713Z","datePublished":"2025-12-26T11:40:10.713Z","dateModified":"2025-12-26T11:40:10.713Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Smooth Flexbox Animation During Scrolling: FLIP Technique","description":"Learn how to create smooth animations for changing flexbox element order during scrolling using the FLIP technique. Overcome CSS order property limitations with JavaScript.","keywords":["flexbox animation","smooth flexbox","scroll animation","CSS order property","flexbox transition","flexbox reorder","FLIP technique","CSS transforms","JavaScript animation","scroll triggered animation","flexbox layout","CSS animation","web animation","frontend development","performance optimization"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/why-not-use-html-tables-for-layout-css-grid-flexbox","name":"Why Avoid HTML Tables for Layout: CSS Grid & Flexbox","position":2,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/why-not-use-html-tables-for-layout-css-grid-flexbox","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/why-not-use-html-tables-for-layout-css-grid-flexbox"},"inLanguage":"en","dateCreated":"2026-01-28T17:35:21.258Z","datePublished":"2026-01-28T17:35:21.258Z","dateModified":"2026-01-28T17:35:21.258Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Why Avoid HTML Tables for Layout: CSS Grid & Flexbox","description":"Discover valid arguments against using HTML tables for layout, including accessibility issues and poor performance. Learn modern CSS best practices with Grid, Flexbox, and semantic HTML for responsive web design.","keywords":["html tables layout","css grid","flexbox","semantic html","modern css layout","web accessibility","responsive design","css grid template areas","flexbox alignment"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/physical-screen-size-css-media-queries","name":"Physical Screen Size in CSS Media Queries Guide","position":3,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/physical-screen-size-css-media-queries","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/physical-screen-size-css-media-queries"},"inLanguage":"en","dateCreated":"2026-02-08T14:08:54.481Z","datePublished":"2026-02-08T14:08:54.481Z","dateModified":"2026-02-08T14:08:54.481Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Physical Screen Size in CSS Media Queries Guide","description":"Learn to account for physical screen size (diagonal) in CSS media queries for responsive web design. Combine resolution (dpi, dppx), viewport width, and device characteristics to differentiate monitors from tablets with same resolution like 1024x768.","keywords":["css media queries","physical screen size","responsive web design","resolution dpi","dppx media query","device pixel ratio","screen resolution css","adaptive layouts","responsive design","dpi dpcm dppx","media query resolution"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll","name":"CSS Sticky Toggle Button: Sliding Animation on Scroll","position":4,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/css-sticky-toggle-button-sliding-animation-scroll"},"inLanguage":"en","dateCreated":"2026-02-06T16:49:41.920Z","datePublished":"2026-02-06T16:49:41.920Z","dateModified":"2026-02-06T16:49:41.920Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"CSS Sticky Toggle Button: Sliding Animation on Scroll","description":"Create smooth sliding animation for CSS sticky toggle button that fills space when hiding content on scroll (>50px). Flexbox, grid, JS solutions with code for position sticky scroll animation CSS and toggle button CSS.","keywords":["css sticky","scroll animation css","toggle button css","sliding animation toggle button scroll","position sticky","sticky button animation","flexbox height transition","grid template rows animation","animate height auto css","hide content on scroll css","css sticky toggle button"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/how-css-negative-margins-work-box-model","name":"How CSS Negative Margins Work in the Box Model","position":5,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/how-css-negative-margins-work-box-model","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/how-css-negative-margins-work-box-model"},"inLanguage":"en","dateCreated":"2025-12-23T15:44:57.139Z","datePublished":"2025-12-23T15:44:57.139Z","dateModified":"2025-12-23T15:44:57.139Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"How CSS Negative Margins Work in the Box Model","description":"Understand how CSS negative margins alter the box model, causing elements to overlap. Learn the underlying mechanism with examples and practical applications.","keywords":["css box model","negative margins","css layout","margin collapse","element overlap","web development","css positioning"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/trigger-css-fade-in-animation-javascript-page-load-link-click","name":"Trigger CSS Fade-In Animation with JavaScript on Page Load & Link Click","position":6,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/trigger-css-fade-in-animation-javascript-page-load-link-click","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/trigger-css-fade-in-animation-javascript-page-load-link-click"},"inLanguage":"en","dateCreated":"2025-12-27T10:12:02.811Z","datePublished":"2025-12-27T10:12:02.811Z","dateModified":"2025-12-27T10:12:02.811Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Trigger CSS Fade-In Animation with JavaScript on Page Load & Link Click","description":"Learn how to trigger a CSS fade-in animation using JavaScript on initial page load and when specific links are clicked. Fix common issues with DOMContentLoaded and event delegation.","keywords":["css animation","fade in css","javascript animation","trigger css animation","page load animation","link click animation","css fade in effect","javascript on page load","event delegation","DOMContentLoaded"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/best-way-center-div-vertically-horizontally-css","name":"Best Way to Center Div Vertically & Horizontally CSS","position":7,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/best-way-center-div-vertically-horizontally-css","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/best-way-center-div-vertically-horizontally-css"},"inLanguage":"en","dateCreated":"2026-02-12T16:35:25.231Z","datePublished":"2026-02-12T16:35:25.231Z","dateModified":"2026-02-12T16:35:25.231Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Best Way to Center Div Vertically & Horizontally CSS","description":"Master center div CSS: Flexbox (display: flex; justify-content: center; align-items: center;) and CSS Grid (place-items: center;) for perfect vertical horizontal centering. Beats margin auto hacks with dynamic content support and broad browser compatibility.","keywords":["center div css","vertical horizontal center css","flexbox center","css grid center","center div vertically horizontally","css centering","margin auto vertical","absolute positioning center"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/how-to-convert-image-to-grayscale-html-css","name":"Convert Image to Grayscale with CSS Filter HTML","position":8,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/how-to-convert-image-to-grayscale-html-css","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/how-to-convert-image-to-grayscale-html-css"},"inLanguage":"en","dateCreated":"2026-01-25T16:20:58.124Z","datePublished":"2026-01-25T16:20:58.124Z","dateModified":"2026-01-25T16:20:58.124Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Convert Image to Grayscale with CSS Filter HTML","description":"Learn how to convert any image to grayscale using only CSS filter property. Simple HTML/CSS method with browser support for Firefox 3+, Safari 3+. Includes hover effects, prefixes, and background image tips for filter css and css grayscale.","keywords":["filter css","css grayscale","grayscale css","css filter grayscale","css image filters","html css filter","css filter color","backdrop filter css"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/css-display-inline-vs-inline-block-differences","name":"CSS Display Inline vs Inline-Block: Key Differences Explained","position":9,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/css-display-inline-vs-inline-block-differences","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/css-display-inline-vs-inline-block-differences"},"inLanguage":"en","dateCreated":"2026-01-31T11:35:03.941Z","datePublished":"2026-01-31T11:35:03.941Z","dateModified":"2026-01-31T11:35:03.941Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"CSS Display Inline vs Inline-Block: Key Differences Explained","description":"Learn the detailed differences between CSS display: inline and display: inline-block properties. Understand how inline-block elements 'behave as a block' while maintaining inline positioning.","keywords":["display inline block","display inline css","css display properties","css box model","vertical alignment css","css layout","css spacing","css positioning","inline vs inline-block","css display types"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/fix-bootstrap-collapse-stuck-collapsing","name":"Fix Bootstrap Collapse Stuck in Collapsing State","position":10,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/fix-bootstrap-collapse-stuck-collapsing","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/fix-bootstrap-collapse-stuck-collapsing"},"inLanguage":"en","dateCreated":"2026-01-02T10:36:54.741Z","datePublished":"2026-01-02T10:36:54.741Z","dateModified":"2026-01-02T10:36:54.741Z","author":[{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}}],"publisher":{"@type":"Organization","@id":"https://neuroanswers.net/about","name":"NeuroAnswers","url":"https://neuroanswers.net/about","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/logo.png","width":"512","height":"512"}},"headline":"Fix Bootstrap Collapse Stuck in Collapsing State","description":"Troubleshoot and fix Bootstrap collapse stuck in 'collapsing' state, failing to hide. Covers duplicate JS, position absolute, floats, event handlers, and initialization issues for reliable Bootstrap navbar collapse and Bootstrap 3 collapse fixes.","keywords":["bootstrap collapse","bootstrap navbar collapse","bootstrap 3 collapse","bootstrap collapse stuck","collapsing state","bootstrap collapse not working","bootstrap collapse hide"],"image":[],"articleBody":""}}]}}]}
Web

Fix CSS/Tailwind Scroll Snap with Sticky Header & Expandable Cards

Resolve CSS/Tailwind scroll snapping issues for expandable cards with a sticky header. Learn why it fails and implement `scroll-padding-top` for perfect alignment.

1 answer 1 view

I am trying to implement CSS/Tailwind scroll snapping for three expandable, vertically stacked cards within a scrollable flex container, positioned below a fixed sticky header. My goal is for the scroll to snap to the top of each card.

However, scroll snapping is not working despite applying overflow-y-auto, snap-y, and snap-mandatory classes to the parent container, and snap-center or snap-start to the child cards.

What is the correct approach to achieve scroll snapping in this specific layout, or what am I missing in my current implementation?

Here is the relevant code:

html
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

<div class="min-h-screen">
  <!-- Header -->
  <div class="p-4 flex gap-2 bg-black h-[64px] sticky top-0 z-10">
  </div>

  <!-- Flex container -->
  <div class="group flex flex-col gap-4 py-4 overflow-y-auto snap-y snap-mandatory">
    <div
      class="card overflow-hidden transition-all duration-300 bg-red-300 snap-center
            h-[calc((100vh-64px-4*1rem)/3)] [&amp;[opened]]:h-[calc(100vh-64px-2*1rem)] overflow-hidden">
      <div class="p-4 flex flex-col h-full">
        <div class="font-bold">Card 1</div>
          <div class="mt-2 flex-1 overflow-auto">
          <table class="w-full border-collapse">
            <tbody>
              <tr>
                <td class="border p-2">Row 1</td>
              </tr>
              <tr>
                <td class="border p-2">Row 2</td>
              </tr>
              <tr>
                <td class="border p-2">Row 3</td>
              </tr>
              <tr>
                <td class="border p-2">Row 4</td>
              </tr>
              <tr>
                <td class="border p-2">Row 5</td>
              </tr>
              <tr>
                <td class="border p-2">Row 6</td>
              </tr>
              <tr>
                <td class="border p-2">Row 7</td>
              </tr>
              <tr>
                <td class="border p-2">Row 8</td>
              </tr>
              <tr>
                <td class="border p-2">Row 9</td>
              </tr>
              <tr>
                <td class="border p-2">Row 10</td>
              </tr>
              <tr>
                <td class="border p-2">Row 11</td>
              </tr>
              <tr>
                <td class="border p-2">Row 12</td>
              </tr>
              <tr>
                <td class="border p-2">Row 13</td>
              </tr>
              <tr>
                <td class="border p-2">Row 14</td>
              </tr>
              <tr>
                <td class="border p-2">Row 15</td>
              </tr>
            </tbody>
          </table>
        </div>
        <button class="mt-2 px-3 py-1 bg-gray-200 rounded self-end"
                onclick="toggleCard(0, this)">
          View more
        </button>
      </div>
    </div>

    <div
      class="card overflow-hidden transition-all duration-300 bg-green-300 snap-center
            h-[calc((100vh-64px-4*1rem)/3)] [&amp;[opened]]:h-[calc(100vh-64px-2*1rem)] overflow-hidden">
     <div class="p-4 flex flex-col h-full">
        <div class="font-bold">Card 2</div>
          <div class="mt-2 flex-1 overflow-auto">
          <table class="w-full border-collapse">
            <tbody>
              <tr>
                <td class="border p-2">Row 1</td>
              </tr>
              <tr>
                <td class="border p-2">Row 2</td>
              </tr>
              <tr>
                <td class="border p-2">Row 3</td>
              </tr>
              <tr>
                <td class="border p-2">Row 4</td>
              </tr>
              <tr>
                <td class="border p-2">Row 5</td>
              </tr>
              <tr>
                <td class="border p-2">Row 6</td>
              </tr>
              <tr>
                <td class="border p-2">Row 7</td>
              </tr>
              <tr>
                <td class="border p-2">Row 8</td>
              </tr>
              <tr>
                <td class="border p-2">Row 9</td>
              </tr>
              <tr>
                <td class="border p-2">Row 10</td>
              </tr>
              <tr>
                <td class="border p-2">Row 11</td>
              </tr>
              <tr>
                <td class="border p-2">Row 12</td>
              </tr>
              <tr>
                <td class="border p-2">Row 13</td>
              </tr>
              <tr>
                <td class="border p-2">Row 14</td>
              </tr>
              <tr>
                <td class="border p-2">Row 15</td>
              </tr>
            </tbody>
          </table>
        </div>
        <button class="mt-2 px-3 py-1 bg-gray-200 rounded self-end"
                onclick="toggleCard(1, this)">
          View more
        </button>
      </div>
    </div>

    <div
      class="card overflow-hidden transition-all duration-300 bg-blue-300 snap-center
            h-[calc((100vh-64px-4*1rem)/3)] [&amp;[opened]]:h-[calc(100vh-64px-2*1rem)] overflow-hidden">
      <div class="p-4 flex flex-col h-full">
        <div class="font-bold">Card 3</div>
          <div class="mt-2 flex-1 overflow-auto">
          <table class="w-full border-collapse">
            <tbody>
              <tr>
                <td class="border p-2">Row 1</td>
              </tr>
              <tr>
                <td class="border p-2">Row 2</td>
              </tr>
              <tr>
                <td class="border p-2">Row 3</td>
              </tr>
              <tr>
                <td class="border p-2">Row 4</td>
              </tr>
              <tr>
                <td class="border p-2">Row 5</td>
              </tr>
              <tr>
                <td class="border p-2">Row 6</td>
              </tr>
              <tr>
                <td class="border p-2">Row 7</td>
              </tr>
              <tr>
                <td class="border p-2">Row 8</td>
              </tr>
              <tr>
                <td class="border p-2">Row 9</td>
              </tr>
              <tr>
                <td class="border p-2">Row 10</td>
              </tr>
              <tr>
                <td class="border p-2">Row 11</td>
              </tr>
              <tr>
                <td class="border p-2">Row 12</td>
              </tr>
              <tr>
                <td class="border p-2">Row 13</td>
              </tr>
              <tr>
                <td class="border p-2">Row 14</td>
              </tr>
              <tr>
                <td class="border p-2">Row 15</td>
              </tr>
            </tbody>
          </table>
        </div>
        <button class="mt-2 px-3 py-1 bg-gray-200 rounded self-end"
                onclick="toggleCard(2, this)">
          View more
        </button>
      </div>
    </div>
  </div>
</div>
javascript
const cards = document.querySelectorAll('.card');

function toggleCard(index, btn) {
    const card = cards[index];
    const isOpen = card.hasAttribute('opened');

    if (isOpen) {
      card.removeAttribute('opened');
      btn.textContent = 'View more';
    } else {
      card.setAttribute('opened', '');
      btn.textContent = 'View less';
    }
}

CSS scroll snap fails in your Tailwind layout because the flex container lacks an explicit height, preventing actual scrolling despite overflow-y-auto. With a sticky header, snap points align to the viewport top instead of below the header—fix this using scroll-padding-top: 64px on the container. Switch child cards to snap-start for top alignment and snap-proximity over snap-mandatory to handle expandable heights gracefully, as detailed in the Tailwind CSS docs.

Contents

Why CSS Scroll Snap Isn’t Working

You’ve got the basics down—snap-y snap-mandatory on the parent and snap-center on kids—but scroll snapping demands a scrollable container first. Your outer min-h-screen div lets the inner flex container expand to fit all three cards, so no overflow happens. No scroll, no snap.

The MDN scroll snap guide nails this: enable scrolling with a fixed-size container and overflow. Your cards’ calc heights look smart (h-[calc((100vh-64px-4*1rem)/3)]), but without capping the parent, it just grows. Sticky header adds insult: snaps hit under the black bar at top-0.

Real-world snag? Expandable cards. When you click “View more,” heights jump via [&[opened]]:h-[...], but mandatory forces awkward snaps if a card overflows viewport height. Users flick-scroll past content stuck behind the header.

Fix 1: Give Your Scroll Container an Explicit Height

Start here. Replace min-h-screen wrapper behavior by setting the flex container to viewport height minus header: h-[calc(100vh-64px)]. Drop the py-4 padding or fold it into calcs for precision.

Why? Flexbox kids with gap-4 need a bounded parent to trigger overflow-y-auto. The Andromeda Galactic blog debugged this exact flex-column fail—no height meant no snap points calculated.

Update your container:

html
<div class="flex flex-col gap-4 h-[calc(100vh-64px)] overflow-y-auto snap-y snap-proximity scroll-py-16">

h-[calc(100vh-64px)] ensures it scrolls. Tailwind’s snap-proximity (we’ll tweak snap type next) previews nicely.

Test it: Without this, mousewheel or touch-flick does nothing snap-like. With it, momentum-scroll hints at snapping.

Fix 2: Offset Snap Points with Scroll Padding for Sticky Headers

Sticky headers hijack viewport top. Browsers snap child tops to 0, burying them under your 64px black bar. Solution: scroll-padding-top: 64px (Tailwind: scroll-pt-16 since 1rem=16px).

The CSS-Tricks practical guide calls this essential for fixed headers: it pads snap calculations, so first card aligns 64px down.

Your updated container becomes:

scroll-pt-16

Full class: h-[calc(100vh-64px)] overflow-y-auto snap-y snap-proximity scroll-pt-16

Pro tip: Match exactly—h-16 header? scroll-pt-16. Dynamic headers? JS to update scroll-padding-top.

Now scrolls snap below the header. Without it, first card’s title vanishes under black.

Fix 3: Use the Right Snap Alignment and Type on Cards

Snap-center centers cards—great for galleries, wrong for top-aligned reads. Switch to snap-start: aligns element’s top to container’s snap port (post-padding).

Container: snap-y snap-proximity
Cards: snap-start

Proximity > mandatory. Mandatory forces snaps always—even mid-tall card. Proximity snaps on momentum end, natural for reading. Tailwind docs warn: mandatory breaks tall kids.

Your cards:

html
<div class="... snap-start h-[calc((100vh-80px)/3)] [&[opened]]:h-[calc(100vh-64px)]">

Tweaked calc: subtract header + padding. gap-4 (1rem each) influences, but viewport math keeps thirds tidy closed, full-height open.

Stack Overflow threads like this flex-scroll fix echo: flex needs flex-shrink-0 on kids if needed, but your fixed heights handle it.

Fix 4: Handle Expandable Cards Without Breaking Snaps

Your toggle adds rows via attribute, ballooning height. Snap-mandatory fights this—snaps to top or next, hiding expanded content.

Stick with proximity: user scrolls freely inside open card, snaps to next on fling.

Refine heights:

  • Closed: Roughly 1/3 viewport minus header/padding: h-[calc((100vh-80px)/3)] (64px header + 16px py-4).
  • Open: Near-full: h-[calc(100vh-64px)] (minus header, no padding bleed).

Add scroll-snap-stop: always (Tailwind arbitrary: snap-stop-always) on cards if you want forced stops on short flings, but proximity usually wins.

Edge case: Open card taller than container? Proximity lets overscroll; mandatory traps users. MDN forbids mandatory on overflow-prone kids.

Your tables already overflow-auto internally—perfect, content scrolls within card.

Complete Working Code Example

Here’s your code fixed. Copy-paste ready (add your toggle JS):

html
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

<div class="min-h-screen bg-gray-100">
  <!-- Sticky Header -->
  <div class="h-16 p-4 flex gap-2 bg-black sticky top-0 z-10 flex-shrink-0">
    <span class="text-white font-bold">Sticky Header</span>
  </div>

  <!-- Scroll Snap Container -->
  <div class="flex flex-col gap-4 px-4 pb-4 h-[calc(100vh-64px)] overflow-y-auto snap-y snap-proximity scroll-pt-16">
    
    <!-- Card 1 -->
    <div class="card overflow-hidden transition-all duration-300 bg-red-300 snap-start 
                h-[calc((100vh-80px)/3)] [&[opened]]:h-[calc(100vh-64px)]">
      <div class="p-4 flex flex-col h-full">
        <div class="font-bold text-lg mb-2">Card 1</div>
        <div class="flex-1 overflow-auto">
          <table class="w-full border-collapse">
            <!-- Your 15 rows here -->
            <tbody>
              <tr><td class="border p-2">Row 1</td></tr>
              <!-- ... abbreviated for brevity ... -->
            </tbody>
          </table>
        </div>
        <button class="mt-4 px-4 py-2 bg-gray-200 rounded self-end hover:bg-gray-300 transition"
                onclick="toggleCard(0, this)">View more</button>
      </div>
    </div>

    <!-- Repeat for Card 2 (green-300) and Card 3 (blue-300) with same structure -->
    <!-- Card 2 -->
    <div class="card overflow-hidden transition-all duration-300 bg-green-300 snap-start 
                h-[calc((100vh-80px)/3)] [&[opened]]:h-[calc(100vh-64px)]">
      <!-- Identical inner structure -->
    </div>

    <!-- Card 3 -->
    <div class="card overflow-hidden transition-all duration-300 bg-blue-300 snap-start 
                h-[calc((100vh-80px)/3)] [&[opened]]:h-[calc(100vh-64px)]">
      <!-- Identical inner structure -->
    </div>
  </div>
</div>

<script>
const cards = document.querySelectorAll('.card');
function toggleCard(index, btn) {
  const card = cards[index];
  const isOpen = card.hasAttribute('opened');
  if (isOpen) {
    card.removeAttribute('opened');
    btn.textContent = 'View more';
  } else {
    card.setAttribute('opened', '');
    btn.textContent = 'View less';
  }
}
</script>

Scrolls snap to each card top, below header. Expand one—snaps gracefully to siblings.

Common Pitfalls and Testing Tips

  • No mobile snap? Add -webkit-overflow-scrolling: touch arbitrary class.
  • Calc precision: Use devtools to measure—100vh includes bars sometimes.
  • Flex gaps: They add to total height; test with gap-0.
  • Browser quirks: Chrome/Safari solid; Firefox tweak scroll-snap-type: y proximity.
  • Test: Trackpad fling, mobile swipe. Stack Overflow flex scroll flags flex-shrink needs.

Tweak heights for your padding. Works on latest browsers (2025).

Sources

Conclusion

Nail CSS scroll snap with a height-bounded container, scroll-pt-16 for your sticky header, snap-start on cards, and snap-proximity for expandables. This setup delivers smooth, top-aligned snaps that respect your layout—try the code and fling-scroll to feel it click. Scale it to more cards or dynamic heights with the same principles.

Authors
Verified by moderation
NeuroAnswers
Moderation
Fix CSS/Tailwind Scroll Snap with Sticky Header & Expandable Cards