transition-left
is not a default Tailwind class configuration. When setting a non-zero transition-duration
using a duration-*
class, it automatically implies transition-property: all
, making the use of transition-left
unnecessary.
To ensure the transition occurs, you must keep the element in the DOM:
const { useState } = React;
const buttonStyle = '';
function DivModal() {
const [isOpen, setIsOpen] = useState(true)
return (
<div>
<button onClick={() => setIsOpen(prev => !prev)} className={buttonStyle}>Open</button>
<div className='fixed top-0 left-0 w-full h-full bg-black/50'>
<div className={`fixed top-0 ${isOpen ? 'left-0' : '-left-full'} flex duration-1000 ease-in-out`}>
<ul className='bg-white'>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
<button onClick={() => setIsOpen(prev => !prev)}>close</button>
</div>
</div>
</div>
)
}
ReactDOM.createRoot(document.getElementById('app')).render(<DivModal/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
<div id="app"></div>
We can enhance this further by hiding the bg-black/50
overlay when the inner drawer is hidden, possibly with a fading effect:
const { useState } = React;
const buttonStyle = '';
function DivModal() {
const [isOpen, setIsOpen] = useState(true)
return (
<div>
<button onClick={() => setIsOpen(prev => !prev)} className={buttonStyle}>Open</button>
<div className={`fixed top-0 left-0 w-full h-full bg-black/50 duration-200 ${isOpen ? '' : 'invisible bg-black/0'}`}>
<div className={`fixed top-0 ${isOpen ? 'left-0' : '-left-full'} flex duration-1000 ease-in-out`}>
<ul className='bg-white'>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
<button onClick={() => setIsOpen(prev => !prev)}>close</button>
</div>
</div>
</div>
)
}
ReactDOM.createRoot(document.getElementById('app')).render(<DivModal/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
<div id="app"></div>
Another optimization to consider is using transform: translateX()
instead of left
. This change improves the transition performance as the browser does not need to recalculate layout every animation frame. Using transform: translateX(-100%)
ensures the drawer only slides its width, eliminating delays associated with viewport-width transitions. This allows for synchronizing the durations of both the overlay and the drawer:
const { useState } = React;
const buttonStyle = '';
function DivModal() {
const [isOpen, setIsOpen] = useState(true)
return (
<div>
<button onClick={() => setIsOpen(prev => !prev)} className={buttonStyle}>Open</button>
<div className={`fixed top-0 left-0 w-full h-full bg-black/50 duration-500 ${isOpen ? '' : 'invisible bg-black/0'}`}>
<div className={`fixed top-0 ${isOpen ? '' : '-translate-x-full'} flex duration-500 ease-out`}>
<ul className='bg-white'>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
<button onClick={() => setIsOpen(prev => !prev)}>close</button>
</div>
</div>
</div>
)
}
ReactDOM.createRoot(document.getElementById('app')).render(<DivModal/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
<div id="app"></div>