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>
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
.
minmax()
function - MDN Docsmin-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.
vh
, svh
, lvh
, dvh
- MDN Docsh-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. |
![]() |
![]() |
![]() |
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.