Image and Delete Modal Components with React and Tailwind CSS
Modals are essential components in SaaS applications (dashboards, CMS, LMS). It is used for confirmations, alerts, or additional interactions. A well-designed modal can enhance user experience while maintaining a clean and reusable codebase.
In this guide, I’ll show you how to create a reusable modal component using React and Tailwind CSS. We’ll follow best React practices by first building a customizable wrapper component for the modal, which will help us create a basic outline (overlay + close button) for each modal component.
Whether you’re developing a personal project, working for a client, or enhancing your design system, this tutorial simplifies modal management in React applications.
Creating the Modal Wrapper Component
The ModalWrapper
is the foundation for all modals in your application. It provides the structure (overlay, close button) and ensures consistent behavior.
The ModalWrapper
acts as a base structure for all modals. It handles the overlay and ensures consistent styling across different modals in your app.
It accepts children as props, we can create any modal content of out it. We don't have to repeat the code for basic styling every time.
Code for ModalWrapper
1import { X } from 'lucide-react' 2import React from 'react' 3import useDisableScroll from '../../hooks/useDisableScroll' 4import useClickOutside from '../../hooks/useClickOutside' 5 6type ModalWrapperProps = { 7 children: React.ReactNode 8 onClose: () => void 9} 10 11const ModalWrapper: React.FC<ModalWrapperProps> = ({ children, onClose }) => { 12 const modalRef = useClickOutside(onClose) 13 useDisableScroll() 14 15 return ( 16 <div className="fixed inset-0 z-[9999] flex h-full w-full items-center justify-center bg-black bg-opacity-50"> 17 <div 18 ref={modalRef} 19 className="animate-zoom relative min-h-[200px] w-[40%] max-w-md rounded-2xl bg-white p-10 shadow-lg sm:w-11/12 sm:p-8" 20 > 21 <button 22 className="absolute right-4 top-4 text-gray-500 hover:text-gray-800" 23 onClick={onClose} 24 aria-label="Close" 25 > 26 <X size={24} /> 27 </button> 28 {children} 29 </div> 30 </div> 31 ) 32} 33 34export default ModalWrapper
The above modal wrapper component has three functionalities:
- Animates the modal on open and close.
- Disables scrolling.
- Closes the modal when clicking outside.
Whatever modal we create from the modal wrapper will include all the above functionalities.
Adding Modal Animations in Tailwind CSS
We added animations to our modal using in tailwind.config.js
. We created a zoom effect that makes the modal grow when it opens.
1/** @type {import('tailwindcss').Config} */ 2export default { 3 content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 theme: { 5 extend: { 6 animation: { 7 zoom: 'zoom 0.1s ease-in-out', 8 }, 9 keyframes: { 10 zoom: { 11 '0%': { 12 transform: 'scale(0)', 13 }, 14 '100%': { 15 transform: 'scale(1)', 16 }, 17 }, 18 }, 19 }, 20 }, 21 plugins: [], 22}
Disable Scroll When Modal Opens
To stop the background from scrolling when the modal is open, we created a custom hook useDisableScroll
that set the overflow
style of the body to hidden
. Once the modal is closed, reset the overflow
style to unset
.
1import { useEffect } from 'react' 2 3const useDisableScroll = () => { 4 useEffect(() => { 5 document.body.style.overflow = 'hidden' 6 7 return () => { 8 document.body.style.overflow = 'unset' 9 } 10 }) 11} 12 13export default useDisableScroll
Close Modal on Outside Click
To close the modal when clicking outside of it, we created a custom hook useClickOutside
that check if the click happened outside the modal's container. If it did, we trigger the close function.
1import { useEffect, useRef } from 'react' 2 3const useClickOutside = (handler: () => void) => { 4 const ref = useRef<HTMLDivElement | null>(null) 5 6 useEffect(() => { 7 const handleClickOutside = (event: MouseEvent) => { 8 if (ref.current && !ref.current.contains(event.target as Node)) { 9 handler() 10 } 11 } 12 13 document.addEventListener('mousedown', handleClickOutside) 14 return () => { 15 document.removeEventListener('mousedown', handleClickOutside) 16 } 17 }, [handler]) 18 19 return ref 20} 21 22export default useClickOutside
Delete Modal Component with Tailwind CSS
This modal is specifically for confirmations before delete an item from an app. It uses the ModalWrapper
to display its content and actions.
It includes:
- A semi-transparent background overlay.
- A close icon in the top-right corner.
- Title to explain the modal action.
- Action buttons for "Delete" and "Cancel."
Code for Delete Modal
1import React from 'react' 2import ModalWrapper from './ModalWrapper' 3 4interface DeleteModalProps { 5 handleDeleteModal: () => void 6} 7 8const DeleteModal: React.FC<DeleteModalProps> = ({ handleDeleteModal }) => { 9 return ( 10 <ModalWrapper onClose={handleDeleteModal}> 11 <div className="mx-auto max-w-72"> 12 <p className="text-center text-xl font-extrabold leading-relaxed text-black"> 13 Are you sure you want to delete this task? 14 </p> 15 <div className="mt-6 flex justify-center gap-6"> 16 <button 17 className="rounded-xl bg-[#713FFF] px-7 py-2 text-base font-semibold text-white shadow-lg" 18 onClick={handleDeleteModal} 19 > 20 Delete 21 </button> 22 <button 23 className="rounded-xl border border-[#d8e0f0] bg-white px-7 py-2 text-base font-normal text-[#7d8592] shadow-sm" 24 onClick={handleDeleteModal} 25 > 26 Cancel 27 </button> 28 </div> 29 </div> 30 </ModalWrapper> 31 ) 32} 33 34export default DeleteModal
How to Use in a Parent Component
Here’s an example of how to use the DeleteModal in your application:
We are showing a modal using an "Open Modal" button in the UI. This is only for the demonstration purposes.
1import React, { useState } from 'react' 2import Button from './components/Button' 3import DeleteModal from './components/Modal/DeleteModal' 4 5const App: React.FC = () => { 6 const [showModal, setShowModal] = useState(false) 7 8 const handleDeleteModal = () => { 9 setShowModal(!showModal) 10 } 11 12 return ( 13 <div className="flex h-[100vh] w-full items-center justify-center"> 14 <Button onClick={handleDeleteModal}>Delete Task</Button> 15 16 {showModal && <DeleteModal handleDeleteModal={handleDeleteModal} />} 17 </div> 18 ) 19} 20 21export default App
React Tailwind Image Modal
Image Modal Preview
Tailwind Image Modal Component Code
We created Image Modal component from ModalWrapper
, so it has all the features.
1import React from 'react' 2import ModalWrapper from './ModalWrapper' 3 4type ImageModalProps = { 5 image: string 6 alt: string 7 onClose: () => void 8} 9 10const ImageModal: React.FC<ImageModalProps> = ({ image, alt, onClose }) => { 11 return ( 12 <ModalWrapper onClose={onClose}> 13 <div className="flex flex-col items-center justify-center pt-1"> 14 <img 15 src={image} 16 alt={alt} 17 className="max-h-[500px] max-w-full rounded-xl object-cover" 18 /> 19 <p className="mt-2 text-lg text-gray-600">{alt}</p> 20 </div> 21 </ModalWrapper> 22 ) 23} 24 25export default ImageModal
How to Use Image Modal in Parent Component
1import React, { useState } from 'react' 2import Button from './components/Button' 3import ImageModal from './components/Modal/ImageModal' 4 5const App: React.FC = () => { 6 const [showModal, setShowModal] = useState(false) 7 8 const handleImageModal = () => { 9 setShowModal(!showModal) 10 } 11 12 return ( 13 <div> 14 <div className="flex h-[100vh] w-full items-center justify-center"> 15 <Button onClick={handleImageModal}>Open Image Modal</Button> 16 </div> 17 {showModal && ( 18 <ImageModal 19 image="https://plus.unsplash.com/premium_photo-1673984261110-d1d931e062c0?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" 20 alt="Project Cover" 21 onClose={handleImageModal} 22 /> 23 )} 24 </div> 25 ) 26} 27 28export default App
Did it help? Let me know via LinkedIn, I would happy to have your feedback.