I'm making a react app with tailwindcss
, and I want to make a hidden mobile navbar and when the user click on the icon it appears.
So I want to make a transition while the menu appears.
I use:
- React
- Tailwindcss
- Headlessui
My Code:
MobileMenu.js:
function MobileMenu() {
return (
<div className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src=".jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
How I show it in Navbar.js:
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu />}
</>
);
}
export default Navbar;
Thanks in advance!
I'm making a react app with tailwindcss
, and I want to make a hidden mobile navbar and when the user click on the icon it appears.
So I want to make a transition while the menu appears.
I use:
- React
- Tailwindcss
- Headlessui
My Code:
MobileMenu.js:
function MobileMenu() {
return (
<div className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
How I show it in Navbar.js:
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu />}
</>
);
}
export default Navbar;
Thanks in advance!
Share Improve this question asked Jul 14, 2022 at 14:55 M7md-DevM7md-Dev 1931 gold badge2 silver badges15 bronze badges3 Answers
Reset to default 6You can use @framer/motion
package that allows you easily animate elements.
const menuVariants = {
open: {
opacity: 1,
x: 0,
},
closed: {
opacity: 0,
x: '-100%',
},
}
Animations can be changed however you want according to @framer/motion
docs.
And attach variants to your <MobileMenu />
component.
function MobileMenu({isMenuOpen}) {
return (
<motion.div animate={isMenuOpen ? 'open' : 'closed'}
variants={menuVariants}> className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
And you can pass isMenuOpen
variable as a prop.
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu isMenuOpen={mobileMenuOpen}/>}
</>
);
}
export default Navbar;
Your approach is to include or exclude the whole component depending on the visibility state, but a DOM structure change can't be animated. The right approach is to pick a CSS property of an existing element that can change gradually over time, like position and opacity. This solution uses only Tailwind means, no additional dependency for animation required.
As for TailwindCSS, the fundamental mechanics is to declare that CSS changes animate as transitions. Add the transition
class for any change to animate, or pick out specific properties with transition-opacity
or transition-transform
. (The latter for scale, translate, rotate transformations.) Optionally, specify the duration time with duration-300
, if the default of 150 doesn't fit.
The actual transition animation is kicked off by changing CSS properties, which means in Tailwind adding, removing, or replacing one or more classes.
This below is how this might look like. The code toggles animated properties based on the new passed-in prop visible
, which comes from Navbar
and its open button. Both opacity and a translation are animated as transitions. ease-out
and ease-in
are flipped as well depending on if the transition is going in or out.
// MobileMenu.js
import { ImHome } from "react-icons/im";
import { CgProfile } from "react-icons/cg";
import { HiUsers } from "react-icons/hi";
import { clsx } from "clsx";
function MobileMenu({ visible }) {
return (
<div
className={clsx(
"relative block px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700 flex flex-col space-y-5",
"transition duration-200 -z-10 ease-out",
{
"ease-out": visible,
"ease-in": !visible,
"opacity-0": !visible,
"opacity-100": visible,
"-translate-y-full": !visible,
"translate-y-0": visible,
}
)}
>
<div className="flex items-center pb-3 border-b border-slate-700">
<img
src="https://placehold.co/32x32"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="flex items-center">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="flex items-center">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="flex items-center">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
// Navbar.js
import React, { useState } from "react";
import { FiMenu } from "react-icons/fi";
import MobileMenu from "./MobileMenu";
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="relative flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white z-10">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
<MobileMenu visible={mobileMenuOpen} />
</>
);
}
export default Navbar;
Check out the running code as codesandbox.
One potential issue with the provided answer is that state is not being set correctly. See the React Docs about updating state based on prior state value.
<div className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2" onClick={() => setMobileMenuOpen(prevState => !prevState)}>