Let's create an animated Drawer using React and Tailwind CSS
Dima Vyshniakov
Posted on October 14, 2024
Drawer component was popularized by Material UI. It's a sliding panel, often used for navigation and showing additional content. It slides in from the side of the screen, overlaying the main content. This design pattern is convenient for menus, filters, or any elements you want to put in focus without cluttering the main interface.
This is the design we are going to create using React and Tailwind CSS
The Drawer slide in from the left side of the screen based on button click.
Drawer placement
Drawer is a modal component by its nature. So we have to render it outside HTML content flow and on top of it, same as we do with dialogs.
React offers us Portals as a technical solution for this problem. It allows developers to render component children into a DOM node that exists outside the hierarchy of the parent component.
Now we are going to create a component to append portals to document.body
, ensuring that they are rendered on top of main content.
import type {FC, ReactNode} from 'react';
import {useId, useState, useEffect} from 'react';
import {createPortal} from 'react-dom';
export type Props = {
children: ReactNode;
};
export const Portal: FC<Props> = ({children}) => {
// assign unique id to easily find portal in DOM
const id = useId();
const [containerAttached, setContainerAttached] = useState(false);
useEffect(() => {
// check if conatiner attached to the DOM
if (!containerAttached) {
const element = document.createElement('div');
element.id = id;
document.body.appendChild(element);
setContainerAttached(true);
}
// don't forget to remove container when portal unmounts
return () => {
containerAttached && document.getElementById(id)!.remove();
};
}, [id, containerAttached]);
return containerAttached
&& createPortal(children, document.getElementById(id)!, id);
};
Drawer component API
In terms of features, we should be able to control Drawer's open/close state. And we will also provide properties to set the look and feel of it.
Below is the complete API we will implement for the component.
type Props = {
// Control Drawer visibility
isOpen?: boolean;
// Provide an external class name
className?: string;
// Callback to trigger when modal needs to be closed
onDismiss?: () => void;
};
export const Drawer: FC<Props> = ({
isOpen,
children,
className,
onDismiss,
}) => {
// provide user a way to close Drawer
const handleBackdropClick = useCallback(() => {
onDismiss?.();
}, [onDismiss]);
return (
<Portal>
{isOpen && (
<Fragment>
{/* Backdrop */}
<div onClick={handleBackdropClick} />
{/* Drawer content */}
<div>{children}</div>
</Fragment>
)}
</Portal>
);
};
Animate Drawer
In order to match the provided design, we need to render and animate between these two visual states:
Drawer state | Start | Animation | End |
---|---|---|---|
Enter | Not visible to the user. Behind the left border. | Slides right from behind the left corner | Fully visible |
Exit | Fully visible | Slides left behind the left corner | Not visible to the user. Behind the left border. |
In order to implement this, we have to add enter and exit animations. While the former is easy to implement using just CSS, the latter requires us to use javascript to manipulate the DOM. Tailwind's Headless UI library has a convenient Transition component.
const Drawer: FC<Props> = ({ isOpen, children, className }) => {
return (
// `show` controls visibility.
// `appear` plays animation when rendered first time
<Transition show={isOpen} appear={true}>
<TransitionChild>
<div
className={classNames(
// Tailwind classes to stick Drawer to the left side
'fixed bottom-0 left-0 h-dvh',
// Configure transition between states.
'transition duration-300 ease-in',
// `data-[closed]:` selector applies appended
// class name when Drawer is in the closed state.
// `-translate-x-full` moves the element 100% width left
'data-[closed]:-translate-x-full',
// Helpful for component reusability
className,
)}
>
{children}
</div>
</TransitionChild>
</Transition>
);
};
Styling backdrop
Backdrop
is an element rendered between main content and modal element. We have to provide following Tailwind CSS classes to it:
fixed
: Sets position: fixed;
inset-0
: CSS shortcut for top, right, left, bottom: 0;
bg-gray-300/30
: Sets background color and opacity;
backdrop-blur-sm
: Blurs main content.
Full demo
Posted on October 14, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 27, 2024