Let's create an animated Drawer using React and Tailwind CSS

morewings

Dima Vyshniakov

Posted on October 14, 2024

Let's create an animated Drawer using React and Tailwind CSS

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

Drawer design

The Drawer slide in from the left side of the screen based on button click.

Drawer animation

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);
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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

πŸ’– πŸ’ͺ πŸ™… 🚩
morewings
Dima Vyshniakov

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