Building a Custom Slide-Out Drawer with Accessibility and Smooth Animations
Introduction
Slide-out menus, also known as drawers, are a staple of modern user interfaces. They provide an elegant way to present additional content or actions without overwhelming the main screen. In this article, we’ll explore the creation of a custom slide-out drawer component in React, focusing on accessibility, animations, and ease of use.
This custom drawer offers features like focus trapping, scroll locking, keyboard accessibility, and smooth spring-based animations using popular libraries like Framer Motion and react-focus-lock.
Features
1. Smooth Animations with Framer Motion
The drawer uses Framer Motion for its open and close animations. The spring-based transitions provide a polished and fluid experience for users:
- Initial Position: The drawer slides in from the right (x: "100%").
- Open Position: Animates smoothly to x: 0.
- Close Animation: Slides back to x: "100%" when dismissed.
2. Focus Management
Accessibility is a key consideration. Using react-focus-lock, the drawer traps focus within its content when open, ensuring that:
- Keyboard navigation stays confined to the drawer.
- Focus returns to the previously focused element when the drawer is closed.
3. Scroll Locking
To prevent background scrolling when the drawer is open, the component integrates react-remove-scroll. This ensures:
- The content behind the drawer remains fixed.
- The user experience feels more intuitive and controlled.
4. Escape Key Support
Keyboard users can close the drawer by pressing the Escape key, thanks to a custom useEscapeKey hook. This feature enhances usability and adheres to accessibility best practices.
5. Overlay Click-to-Close
An overlay behind the drawer provides an easy way for users to dismiss it. Clicking anywhere on the overlay triggers the onOpenChange callback, closing the drawer.
Code Overview
Here’s a high-level look at the Drawer component:
import React from "react";
import styles from "./Drawer.module.css";
import FocusLock from "react-focus-lock";
import { RemoveScroll } from "react-remove-scroll";
import { useEscapeKey } from "../../hooks/useEscapeKey";
import { AnimatePresence, motion } from "framer-motion";
function Drawer({
children,
onOpenChange,
isOpen,
}: {
children: React.ReactNode;
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
}) {
useEscapeKey(() => {
onOpenChange(false);
});
return (
<>
{isOpen && (
<div
className={styles.overlay}
onClick={() => {
onOpenChange(false);
}}
></div>
)}
<RemoveScroll enabled={isOpen} forwardProps>
<AnimatePresence>
{isOpen ? (
<div className={styles.drawerContainer}>
<FocusLock returnFocus>
<motion.div
className={styles.drawer}
initial={{ x: "100%" }}
animate={{ x: 0 }}
transition={{
duration: 0.2,
type: "spring",
stiffness: 200,
damping: 25,
}}
exit={{ x: "100%" }}
>
<div>{children}</div>
<button
onClick={() => {
onOpenChange(false);
}}
>
Close
</button>
</motion.div>
</FocusLock>
</div>
) : null}
</AnimatePresence>
</RemoveScroll>
</>
);
}
export default Drawer;
How It Works
1. Opening and Closing
The drawer’s visibility is controlled by the isOpen
prop, with onOpenChange
toggling the state. Clicking the overlay or the close button sets isOpen
to false
.
2. Focus Management
Using react-focus-lock
, all focusable elements outside the drawer are locked, ensuring that the user cannot accidentally navigate away.
3. Animation Logic
Framer Motion powers the animations:
- When the drawer opens, it animates from x: "100%" to x: 0.
- On close, it slides back to x: "100%".
4. Scroll Locking
react-remove-scroll
ensures that the background content does not scroll when the drawer is open, enhancing the user experience.
5. Escape Key Support
The useEscapeKey hook listens for the Escape key and triggers the close logic.
Example Usage
Here’s an example of how to use the Drawer component in your application:
import React, { useState } from 'react'
import Drawer from './components/Drawer'
function App() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Drawer</button>
<Drawer isOpen={isOpen} onOpenChange={setIsOpen}>
<div>
<h2>Slide-Out Menu</h2>
<p>Here’s some content inside the drawer!</p>
</div>
</Drawer>
</div>
)
}
export default App
Accessibility Features
1. Focus Trapping
Users navigating with a keyboard will have their focus locked within the drawer when it’s open. This ensures a smooth and accessible experience.
2. Dismiss with Escape Key
Pressing the Escape key provides a quick way to dismiss the drawer, enhancing usability for power users.
3. ARIA Roles and Labels
You can extend the component to include ARIA attributes like role="dialog" and aria-labelledby for better screen reader support.
Challenges and Solutions
Challenge: Managing Focus and Scroll Locking
Solution: By integrating react-focus-lock and react-remove-scroll, the component ensures a seamless experience for both keyboard and mouse users.
Challenge: Smooth Animations
Solution: Framer Motion’s spring transition provides natural and fluid animations.
Demo and Repository
Want to see the slide-out drawer in action? Check out the live demo or explore the code on GitHub.
Conclusion
This custom Slide-Out Drawer strikes a balance between simplicity and usability. With smooth animations, accessibility features, and easy integration, it’s an excellent addition to any modern web application. Whether you’re building a settings menu, a side navigation bar, or a custom modal, this component is both versatile and developer-friendly.
Ready to integrate it into your project? Check out the demo or dive into the code to get started! 🎉