cssreactjsmobiletailwind-csslandscape

Overflow: auto; content not scrollable in mobile landscape view


Hi I am working on a React.js project (totally new to the framework) set up with TailwindCSS. I'm building a collapsing menu bar for mobile version which works as intended in Chrome DevTools (I set up a min-height to the div based on a mobileMenu react state that gets triggered on clicking a menu icon).

Overflowing content is scrollable in DevTools, however when I push the code to Vercel and check it on mobile phone in Safari and switch the device to landscape mode, the menu is not scrollable and it's basically blocked (the collapsing behaviour still works).

I am a bit confused and could use a few hints from a front-end dev.

Here's my code:

<div className={!mobileMenu
  ? "bg-white fixed top-0 w-screen min-h-[400px] overflow-auto transform transition-[min-height] duration-600 ease-in-out origin-top"
  : "bg-white fixed top-0 w-screen min-h-0 max-h-0 transform transition-[min-height] duration-300 ease-in-out origin-top overflow-auto"}
>
  <ul className="mt-20 text-center px-5">
    <li className="text-skyBlue font-semibold border-b-2 pt-4 pb-2"><a href="/">Acceuil</a></li>
    <li className="text-skyBlue font-semibold border-b-2 p-2"><a href="/">Covoiturages</a></li>
    <li className="text-skyBlue font-semibold border-b-2 p-2"><a href="/">Contact</a></li>
    <li className="text-skyBlue font-semibold border-b-2 p-2"><a href="/">
    <i className="text-lg mr-2 bi bi-search text-skyBlue"></i>
    <span className="text-skyBlue">Rechercher</span>
    </a></li>
    <li className="text-skyBlue font-semibold border-b-2 p-2"><a href="/">
    <i className="text-lg mr-2 bi bi-plus-circle-fill text-skyBlue"></i>
    <span className="text-skyBlue">Ajouter un trajet</span>
    </a></li>
    <li className="text-skyBlue font-semibold border-b-2 p-2"><a href="/">
    <i className="text-lg mr-2 bi bi-person-fill text-skyBlue"></i>
    <span className="text-skyBlue">Connexion</span>
    </a></li>
  </ul>
</div>

Solution

  • TLDR: min-h-[minmax(100dvh, 400px)] h-full max-h-dvh

    Your issue is caused by the fact that min-height is a desired height where scrolling is not yet necessary. To make the scroll appear on a landscape mobile screen (where the height is likely smaller than 400px) despite min-h-[400px], you can use a CSS minmax function to determine whether 100vh or 400px is greater in our case. This function selects the smallest of the listed dynamic values, so on larger screens, 400px will be the min-height, while on screens smaller than 400px, the full screen height (100vh) will be used as the min-height.

    min-h-[minmax(100vh, 400px)]
    

    I mentioned 100vh. In general, this refers to the full screen size. However, for example, on mobile devices, the visibility of the browser header can increase or decrease the size of the webpage. As long as the header is visible, the webpage's size will be reduced by the height of the header. To address this issue, the svh, lvh, and dvh values were introduced. The dvh value is the most suitable for your case, as it dynamically adjusts the height in CSS depending on whether the browser header is visible or not.

    h-svh h-lvh h-dvh
    It automatically subtracts the browser's top bar height from the 100vh, even when it's not visible. It automatically maintains the full height including the browser's top bar, even when the top bar is visible. It automatically switches the height to svh or lvh depending on whether the browser's top bar is visible.
    h-svh h-lvh h-dvh
    min-h-[minmax(100dvh, 400px)]
    

    The scrollbar appears because, in the opened state, don't allow the div to be larger than the screen, so use max-h-screen (max-height: 100vh;). Although it's worth considering using max-h-[100dvh] instead; a max-h-dvh (max-height: 100dvh;) class name was recently introduced for it.

    Thus, I moved the animation to the height, which changes from h-0 to h-full when the opened state changes. This way, the min-h and max-h will actually size the menu, but the height ensures the animation.

    min-h-[minmax(100dvh, 400px)] h-full max-h-dvh
    

    const { useState } = React;
    
    function App() {
      const [opened, setOpened] = useState(true);
      const toggleMenu = () => setOpened(!opened);
    
      return (
        <div>
          {/* Button to toggle mobile menu */}
          <button
            className="fixed top-0 left-0 z-10 p-2 m-4 bg-sky-500 text-white rounded-md cursor-pointer"
            onClick={toggleMenu}
          >
            Toggle Menu
          </button>
          
          <div
            className={clsx(
              "bg-white fixed top-0 w-screen transform transition-[height] ease-in-out origin-top",
              opened
                ? "min-h-[minmax(100dvh, 400px)] h-full max-h-dvh duration-600 overflow-auto"
                : "h-0 duration-300 overflow-hidden",
            )}
          >
            <ul className="mt-20 text-center px-5">
              <li className="text-skyBlue font-semibold border-b-2 pt-4 pb-2">
                <a href="/">Acceuil</a>
              </li>
              <li className="text-skyBlue font-semibold border-b-2 p-2">
                <a href="/">Covoiturages</a>
              </li>
              <li className="text-skyBlue font-semibold border-b-2 p-2">
                <a href="/">Contact</a>
              </li>
              <li className="text-skyBlue font-semibold border-b-2 p-2">
                <a href="/">
                  <i className="text-lg mr-2 bi bi-search text-skyBlue"></i>
                  <span className="text-skyBlue">Rechercher</span>
                </a>
              </li>
              <li className="text-skyBlue font-semibold border-b-2 p-2">
                <a href="/">
                  <i className="text-lg mr-2 bi bi-plus-circle-fill text-skyBlue"></i>
                  <span className="text-skyBlue">Ajouter un trajet</span>
                </a>
              </li>
              <li className="text-skyBlue font-semibold border-b-2 p-2">
                <a href="/">
                  <i className="text-lg mr-2 bi bi-person-fill text-skyBlue"></i>
                  <span className="text-skyBlue">Connexion</span>
                </a>
              </li>
            </ul>
          </div>
        </div>
      );
    }
    
    ReactDOM.createRoot(document.getElementById("root")).render(<App />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
    <script src="https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.min.js"></script>
    
    <style type="text/tailwindcss">
    @theme {
      --color-skyBlue: var(--color-sky-600);
    }
    </style>
    
    <div id="root"></div>

    Note: I placed the toggle menu button fixed in the top-left corner just for the example, so the menu state can be easily changed. Regardless, the menu’s scrollability remains fully testable.

    Note: This is extra and indeed not directly related to the question. I integrated a clsx example into my answer so that it's clearer which class is used constantly and which ones should be used based on the opened state being true or false, thus avoiding code duplication.