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. |
 |
 |
 |
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/[email protected]/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.