Table of contents
Modal has become a ubiquitous UI element in modern web designs. It is a simple yet effective design choice with respect to UX. I believe everyone is well aware of how omnipresent a modal element is the everyday web. Today we will be building a Modal Component in react js and build it in a way that it can be re-used.
Let's look at what we are going to build first.
I’m assuming that you’re familiar with how to create a React app and basic styling with CSS.
Requirments
First, let's set some specific requirements before we can start building the components.
- Modal component should be easily resized based on props.
- Close modal by outside click.
- Close modal on Escape button press.
- Modal content should be dynamic as it can be changed easily.
Okay, now we can start building the layout.
Create a basic modal layout
A modal is usually a collection of 4 parts.
- Overlay: Usually a transparent backdrop container covers the whole screen.
- Header: Contains modal title and close icon (if any).
- Body: display the modal content.
- Footer: display the action buttons. (optional)
Let's start building the components
Note:- I will be using building the component with SCSS and CSS Modules. Feel free to stick with CSS if preferable.
First, we will create two files one for component and one for SCSS.
import styles from "./modal.module.scss";
import { RiCloseFill } from "react-icons/ri";
import classNames from "classnames";
const Modal = ({
size = "md", // Width of the modal container
title, // Title of the modal in header part
isOpen, // Modal state (true || false)
closeOnEsc = true, // If true the modal can be closed on escape key click
closeOnOverlayClick = true, // If true modal can be closed on overlay click
onClose, // a function to perform close action for the modal
overlayClassName, // Pass classes directly to overlay container
headerClassName, // Pass classes directly to header
children // Body elements of the modal
}) => {
const containerClass = classNames(
styles.container,
styles[size],
overlayClassName
);
const headerClass = classNames(styles.header, headerClassName);
return(
<div
className={containerClass}
tabIndex="-1"
role="dialog"
aria-labelledby="exampleModalCenterTitle"
aria-hidden="true"
>
<div className={styles.content}>
<div className={headerClass}>
<span className={styles.title}>{title}</span>
<span onClick={onClose} className={styles.close}>
<RiCloseFill />
</span>
</div>
{children}
</div>
</div>
)
}
export { Modal }
Here we have declared a few props which will sort of configure the modal component as the requirements specified.
Then we are calculating which classes to put in the container element based on the props given.
Rest is JSX to structure our modal component as per our requirement. Now let's style the component.
Styling
.container {
position: fixed;
z-index: 95;
display: flex;
justify-content: center;
align-items: center;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
backdrop-filter: blur(2px);
background-color: #00000070;
transition: all 0.3s ease-in-out;
.content {
background-color: var(--clr-theme-1);
margin: auto;
padding: 1.2rem;
width: 100%;
border-radius: 1rem;
box-shadow: var(--box-shadow-1);
}
&.full .content {
max-width: 100vw;
min-height: 100vh;
}
&.xxl .content {
max-width: 60rem;
}
&.xl .content {
max-width: 44rem;
}
&.lg .content {
max-width: 38rem;
}
&.md .content {
max-width: 32rem;
}
&.sm .content {
max-width: 26rem;
}
&.xs .content {
max-width: 20rem;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.title {
font-size: 500;
font-weight: 600;
}
.body {
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 80vh;
}
.close {
color: var(--clr-gray-2);
background: none;
font-size: 1.6rem;
&:hover,
&:focus {
color: var(--clr-gray-1);
background: var(--clr-theme-2);
}
&:active {
transform: scale(0.9);
}
}
}
:global(body:not(.dark-mode)) .container {
--clr-theme-3: var(--clr-gray-3);
}
We have different classes to resize the body of the modal. we can pass the required size through props.
The modal component is somewhat ready to render. let's add it to our App.js
file.
import { Modal } from "./Modal/Modal";
import { useState } from "react";
export default function App() {
const [isModalOpen, setModalState] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setModalState(true)}>Open modal</button>
<Modal
size="md"
isOpen={isModalOpen}
onClose={() => setModalState(false)}
title="My Playlists"
>
<h1>Hello</h1>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Temporibus
libero ex et ipsum quam itaque illo, modi corrupti aspernatur earum
cum tempora quae dicta, commodi non nemo hic? Laudantium, odit?
</p>
</Modal>
</div>
);
}
Add features
Our basic modal component is ready, now let's add some functionality like on Escape click modal should get closed.
const escFunction = useCallback((event) => {
if (event.keyCode === 27) {
onClose();
}
}, []);
useEffect(() => {
closeOnEsc && document.addEventListener("keydown", escFunction);
return () => {
closeOnEsc && document.removeEventListener("keydown", escFunction);
};
}, [isOpen, closeOnEsc, escFunction]);
Here we are adding an event listener to detect escape key press in useEffect
.
Now let's add another feature. as per our requirements, the modal also should get closed on the overlay
click. To achieve this we can use a hook called useOnClickOutside
. If you want to know why and how this hook works you can refer to this blog
It's pretty simple. the hook takes an element and adds an event listener to check if any click event is triggered outside that element.
const onClickOutsideRef = useOnClickOutside({ handler: onClose });
let contentRef = useRef();
contentRef = closeOnOverlayClick ? onClickOutsideRef : contentRef;
Baes on the closeOnOverlayClick
prop we are adding ref to our overlay element and passing onClose
function as an argument.
Here we go we have a functional re-usable Modal component. Hope you liked the blog.