\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/how-to-convert-image-to-grayscale-html-css","name":"Convert Image to Grayscale with CSS Filter HTML","position":3,"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/absolute-positioning-css-responsive-layout","name":"Absolute Positioning CSS for Responsive Layout","position":4,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/absolute-positioning-css-responsive-layout","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/absolute-positioning-css-responsive-layout"},"inLanguage":"en","dateCreated":"2025-12-28T15:48:26.123Z","datePublished":"2025-12-28T15:48:26.123Z","dateModified":"2025-12-28T15:48:26.123Z","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":"Absolute Positioning CSS for Responsive Layout","description":"Learn how to create HTML layouts with absolute positioning CSS and responsive tiling. Master CSS techniques for fixed left div and flexible right-side containers.","keywords":["absolute positioning css","responsive layout css","css position absolute","html css layout","flexbox css","css grid","responsive design","css positioning","html layout","css techniques"],"image":[],"articleBody":""}},{"@type":"ListItem","@id":"https://neuroanswers.net/c/web/q/make-div-not-larger-than-contents","name":"How to Make a Div Not Larger Than Its Contents","position":5,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/make-div-not-larger-than-contents","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/make-div-not-larger-than-contents"},"inLanguage":"en","dateCreated":"2025-10-25T07:33:56.547Z","datePublished":"2025-10-25T07:33:56.547Z","dateModified":"2026-01-02T08:14:36.838Z","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 to Make a Div Not Larger Than Its Contents","description":"Learn CSS techniques to make a div fit its content width, like inline-block, display: table, or width: fit-content. Perfect for wrapping tables without expanding. Includes browser support and examples.","keywords":["div width","fit content","css div","width fit content","table width","display inline-block","div fit content","css div width","table fit content","shrink to fit"],"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/preventing-html-code-displaying-square-boxes","name":"Preventing HTML Code from Displaying as Square Boxes","position":7,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/preventing-html-code-displaying-square-boxes","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/preventing-html-code-displaying-square-boxes"},"inLanguage":"en","dateCreated":"2026-03-27T12:00:57.694Z","datePublished":"2026-03-27T12:00:57.694Z","dateModified":"2026-03-27T13:06:18.516Z","author":[{"@type":"Person","@id":"https://neuroanswers.net/@geoffgraham","name":"Geoff Graham","givenName":"Geoff","familyName":"Graham","url":"https://neuroanswers.net/@geoffgraham","image":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/person/geoffgraham/avatar.png","width":"72","height":"72"},"jobTitle":"Front End Developer","description":"Author of CSS-Tricks articles with expertise in front-end development and CSS techniques"},{"@type":"Organization","@id":"https://neuroanswers.net/@css-tricks-com","name":"CSS-Tricks","description":"A website about making websites, focusing on CSS techniques, web standards, and front-end development best practices","url":"https://neuroanswers.net/@css-tricks-com","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/source/css-tricks-com/icon.png","width":"72","height":"72"}},{"@type":"Organization","@id":"https://neuroanswers.net/@developer-mozilla-org","name":"MDN Web Docs","description":"Free web developer documentation and tutorials","url":"https://neuroanswers.net/@developer-mozilla-org","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/source/developer-mozilla-org/logo.png","width":"72","height":"72"}},{"@type":"Organization","@id":"https://neuroanswers.net/@www-w3-org","name":"W3C","description":"The World Wide Web Consortium (W3C) is the main international standards organization for the World Wide Web, developing open standards to ensure the long-term growth of the Web","url":"https://neuroanswers.net/@www-w3-org","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/source/www-w3-org/logo.png","width":"72","height":"72"}},{"@type":"Organization","@id":"https://neuroanswers.net/@web-dev","name":"Web.dev","description":"Google's resource for web developers, providing articles, guides, and best practices for modern web development","url":"https://neuroanswers.net/@web-dev","logo":{"@type":"ImageObject","url":"https://neuroanswers.net/api/v1/source/web-dev/logo.png","width":"72","height":"72"}}],"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":"Preventing HTML Code from Displaying as Square Boxes","description":"Learn how to prevent HTML code from displaying as square boxes in browsers without using special characters. Solutions include font fallback strategies, system font stacks, and proper HTML encoding techniques.","keywords":["html code","square boxes","character encoding","font fallback","system font stack","html encoding","browser display","utf-8","character display","font rendering"],"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":8,"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/css-sticky-toggle-button-sliding-animation-scroll","name":"CSS Sticky Toggle Button: Sliding Animation on Scroll","position":9,"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/css-grid-evenly-space-columns-like-flexbox-space-between","name":"CSS Grid: Even Columns with Space-Between Gaps Flush Edges","position":10,"item":{"@type":"Article","@id":"https://neuroanswers.net/c/web/q/css-grid-evenly-space-columns-like-flexbox-space-between","mainEntityOfPage":{"@type":"WebPage","@id":"https://neuroanswers.net/c/web/q/css-grid-evenly-space-columns-like-flexbox-space-between"},"inLanguage":"en","dateCreated":"2025-12-27T15:38:57.680Z","datePublished":"2025-12-27T15:38:57.680Z","dateModified":"2025-12-27T15:38:57.680Z","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 Grid: Even Columns with Space-Between Gaps Flush Edges","description":"Learn to evenly distribute columns in CSS Grid like Flexbox space-between using gap property and minmax(0,1fr). Perfect for calculator layouts with buttons flush to container edges, no outer padding.","keywords":["css grid","css grid column","grid space between","css grid gap","grid template columns","css grid layout","css grid minmax","responsive css grid","css grid calculator","column-gap css","justify-content grid"],"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