Toast notifications are a popular way to provide users with quick feedback and alerts on actions they take on the web application. While there are many pre-built libraries available for adding toast notifications to your React project, building your own custom component can provide greater flexibility and control over the user experience. In this blog post, I will guide you through the process of building a custom toast notification component using ReactJs and the Context API. You'll learn how to use Context API and the useReducer hook to manage the state of your toast notification component. We'll also show you how to customize the position of your notification and add a progress bar to display the remaining time. Additionally, we'll implement a pause on hover and a dismiss button functionality. By the end of this tutorial, you'll have a fully functional custom toast notification component that you can customize according to your project's design and functionality requirements. So, let's start building! Demo Check out this video demo to see the final Custom Toast Notification Component in action: https://youtu.be/S4UQt50UzGw?embedable=true Cloning the starter code Before we begin building our custom toast notification component, let's clone the starter code from GitHub for our React project. To do this, open up your terminal and navigate to the directory where you want to clone your project. Then, run the following command: git clone https://github.com/rishipurwar1/react-custom-toast-notification.git Once the cloning process is complete, navigate into the project directory by running: cd react-custom-toast-notification You will find the CSS code for this project in the file, which is imported into the file. src/App.css App.js Now, we'll install all the dependencies of our project. Enter the following command in your terminal: npm install This command will install all the required packages listed in the package.json file. Next, we'll start the development server by running: npm start You should see something like this on your browser: Now that we have our React project set up, we can move on to creating our custom toast notification component. Creating the Toast Notification Component We will start by creating a new file in the folder called . Inside the file, we will define our functional component which will return the toast notification markup. src/components Toast.js Toast.js Toast const Toast = () => { return ( <div> {/* toast notification markup */} </div> ) } export default Toast; Next, we will define the markup for our toast notification. In this example, we will create a toast notification with a message, an icon and a dismiss button. import { IconAlertCircleFilled, IconCircleCheckFilled, IconCircleXFilled, IconInfoCircleFilled, IconX, } from "@tabler/icons-react"; const toastTypes = { success: { icon: <IconCircleCheckFilled />, iconClass: "success-icon", progressBarClass: "success", }, warning: { icon: <IconAlertCircleFilled />, iconClass: "warning-icon", progressBarClass: "warning", }, info: { icon: <IconInfoCircleFilled />, iconClass: "info-icon", progressBarClass: "info", }, error: { icon: <IconCircleXFilled />, iconClass: "error-icon", progressBarClass: "error", }, }; const Toast = ({ message, type, id }) => { const { icon, iconClass, progressBarClass } = toastTypes[type]; return ( <div className="toast"> <span className={iconClass}>{icon}</span> <p className="toast-message">{message}</p> <button className="dismiss-btn"> <IconX size={18} color="#aeb0d7" /> </button> </div> ) } export default Toast; In the above code, we have imported some icons from library. Then we have defined an object called to make our toast notification component more flexible. This object contains data for different types of notifications, such as , , , and . Each type has its specific , , and associated with it. While is currently unused, it will be used later to give a background color to the progress bar. You can find the CSS code for all these classes in the file. @tabler/icons-react toastTypes success warning info error icon iconClass progressBarClass progressBarClass App.css The component takes three props - , , and . The prop is used to display the text message of the notification. The prop is used to determine the type of notification and the corresponding icon and icon class name. Toast message type id message type Although the prop is not currently used in the above code, we will use it later to remove the notification. id Finally, we have defined a dismiss button in our Toast component, which will allow the user to remove the notification. Now that we have created the component, let's create a container component called that will hold all the components. Toast ToastsContainer Toast Let's create a new file in the directory and add the following code: ToastsContainer.js src/components import Toast from './Toast'; const ToastsContainer = ({ toasts }) => { return ( <div className="toasts-container"> {toasts.map((toast) => ( <Toast key={toast.id} {...toast} /> ))} </div> ); }; export default ToastsContainer; The component accepts an array of toast objects as the prop. It then maps over this array using the method and renders a component for each object. We are using the spread syntax to pass all the properties of the object, such as , , and , as individual props to the component. ToastsContainer toasts map Toast {...toast} toast message type id Toast We'll render the component inside the component, which we have yet to create. ToastsContainer ToastContextProvider Setting up the Toast Notification Context Now that we have our and components set up, it's time to move on to the next step, which is creating a context for our toast notifications. Toast ToastsContainer First, let's create a new file called in the folder. Inside this file, we'll create a new context using the function provided by React: ToastContext.js src/contexts createContext // ToastContext.js import { createContext } from "react"; export const ToastContext = createContext(); We've created a new using the function, and we've exported it so that we can use it in other parts of our application. ToastContext createContext Now, let's create a component that will wrap our entire application and provide the to all of its children: ToastContextProvider ToastContext // ToastContext.js export const ToastContextProvider = ({ children }) => { return ( <ToastContext.Provider value={{}}> {children} </ToastContext.Provider> ); }; Creating the Toast Notification Reducer Function Next, let's create a new file called in the folder. In this file, we'll create a function to manage the state of the toasts: toastReducer.js src/reducers toastReducer // toastReducer.js export const toastReducer = (state, action) => { switch (action.type) { case "ADD_TOAST": return { ...state, toasts: [...state.toasts, action.payload], }; case "DELETE_TOAST": const updatedToasts = state.toasts.filter( (toast) => toast.id !== action.payload ); return { ...state, toasts: updatedToasts, }; default: throw new Error(`Unhandled action type: ${action.type}`); } }; Our function takes in a and an and returns a new state based on the . We have two types of actions: , which adds a new toast to the array in our state, and , which removes a toast from the array based on its ID. toastReducer state action action type ADD_TOAST toasts DELETE_TOAST toasts Now let's go back to the file and import the function and hook: ToastContext.js toastReducer useReducer // ToastContext.js import { createContext, useReducer} from "react"; import { toastReducer } from "../reducers/toastReducer"; Inside the component, we'll use the hook that takes in the function and : ToastContext.Provider useReducer toastReducer intialState // ToastContext.js const initialState = { toasts: [], }; export const ToastContextProvider = ({ children }) => { const [state, dispatch] = useReducer(toastReducer, initialState); return ( <ToastContext.Provider value={{}}> {children} </ToastContext.Provider> ); }; Now, we need to create some functions inside the component to and toasts from the . Firstly, we'll create an function that takes in and as arguments and dispatches an action to add a new toast to the state: ToastContextProvider add remove state addToast message type ADD_TOAST // ToastContext.js const addToast = (type, message) => { const id = Math.floor(Math.random() * 10000000); dispatch({ type: "ADD_TOAST", payload: { id, message, type } }); }; In addition to the function, we'll create individual functions for each type of toast notification - , , , and . These functions will call the function with the corresponding type: addToast success warning info error addToast // ToastContext.js const success = (message) => { addToast("success", message); }; const warning = (message) => { addToast("warning", message); }; const info = (message) => { addToast("info", message); }; const error = (message) => { addToast("error", message); }; To remove toast notifications, we'll create a function that takes in a toast as an argument and dispatches a action to remove the toast from the state: remove id DELETE_TOAST // ToastContext.js const remove = (id) => { dispatch({ type: "DELETE_TOAST", payload: id }); }; Then, create a object that holds all the functions we have created and pass it to the component: value ToastContext.Provider // ToastContext.js export const ToastContextProvider = ({ children }) => { // rest of the code const value = { success, warning, info, error, remove }; return ( <ToastContext.Provider value={value}> {children} </ToastContext.Provider> ); }; Next, we need to render the component inside the component like this: ToastsContainer ToastContextProvider // import ToastsContainer import ToastsContainer from "../components/ToastsContainer"; export const ToastContextProvider = ({ children }) => { const [state, dispatch] = useReducer(toastReducer, initialState); // rest of the code return ( <ToastContext.Provider value={value}> <ToastsContainer toasts={state.toasts} /> {children} </ToastContext.Provider> ); }; Finally, wrap our component in the component in order to make the context available to all of our child components: App ToastContextProvider // src/index.js import { ToastContextProvider } from "./contexts/ToastContext"; root.render( <React.StrictMode> <ToastContextProvider> <App /> </ToastContextProvider> </React.StrictMode> ); Hook Creating the useToast Next, let's create our custom hook, , in the folder, which will allow us to access the toast-related functions from the directly without having to manually call and import the in every component. useToast.js src/hooks ToastContext useContext ToastContext // useToast.js import { useContext } from 'react'; import { ToastContext } from "../contexts/ToastContext"; export const useToast = () => useContext(ToastContext); The hook is a simple function that utilizes the hook from React to access the . This hook provides a simple and intuitive API for showing different types of toasts in our application since it returns the context's value, which includes all the functions for adding and removing toasts. useToast useContext ToastContext Using the Hook useToast Now that we have created our custom hook , we can use it to show toasts in our components. This hook provides a of the context containing all the toast functions that we defined earlier: , , , , and . useToast value success warning info error remove To use the hook, we simply need to import it into our component and call it to get access to the object. After that, we can assign it to a variable named : App value toast // App.js import { useToast } from "./hooks/useToast"; const App = () => { const toast = useToast(); return ( // JSX ); }; Next, we need to add an event to each of the buttons defined in the component so that when a button is clicked, it should display the corresponding toast notification. onClick App For example, to show a success toast, we would call , where is the text we want to display in the toast. toast.success("MESSAGE") MESSAGE Here's an example of how we can use the hook in an component: useToast App // App.js const App = () => { const toast = useToast(); return ( <div className="app"> <div className="btn-container"> <button className="success-btn" onClick={() => toast.success("Success toast notification")} > Success </button> <button className="info-btn" onClick={() => toast.info("Info toast notification")} > Info </button> <button className="warning-btn" onClick={() => toast.warning("Warning toast notification")} > Warning </button> <button className="error-btn" onClick={() => toast.error("Error toast notification")} > Error </button> </div> </div> ); }; Now, you should be able to create new toast notifications by clicking on those buttons. Using the hook can make adding and removing toasts in our app easier, and it's also a great way to keep your code clean and organized. useToast Check out my open-source project, to take your frontend development skills to the next level for FREE! FrontendPro Adding dismiss button functionality To add this functionality, we'll first import the hook in our component and call the hook to get access to the object: useToast Toast useToast value // Toast.js import { useToast } from "../hooks/useToast"; const Toast = ({ message, type, id }) => { const { toastClass, icon, iconClass } = toastTypes[type]; const toast = useToast() // call useToast return ( <div className={`toast ${toastClass}`}> <span className={iconClass}>{icon}</span> <p className="toast-message">{message}</p> <button className="dismiss-btn"> <IconX size={18} color="#aeb0d7" /> </button> </div> ); }; Next, we'll define a function, which will call the function with a toast to remove the toast. We will then attach an event to the dismiss button to call the function: handleDismiss toast.remove() id onClick handleDismiss const Toast = ({ message, type, id }) => { // code const handleDismiss = () => { toast.remove(id); }; return ( <div className={`toast ${toastClass}`}> <span className={iconClass}>{icon}</span> <p className="toast-message">{message}</p> {/* Add onClick */} <button className="dismiss-btn" onClick={handleDismiss}> <IconX size={18} color="#aeb0d7" /> </button> </div> ); }; With this change, you can now manually remove a toast by clicking the dismiss button. Adding a Progress Bar and Auto-Dismiss Timer In this section, we will add a progress bar that will indicate how much time is remaining before the toast disappears and an auto-dismiss timer to automatically remove toast after a certain amount of time. To implement the auto-dismiss timer functionality, we will use the hook to attach a timer to each toast using when it's mounted. This timer will call the function after a certain amount of time has passed, which will remove the toast from the screen. useEffect setTimeout handleDismiss We can achieve this by adding the following code to our Toast component: // Toast.js import { useEffect, useRef } from "react"; // import useEffect & useRef const Toast = ({ message, type, id }) => { // rest of the code const timerID = useRef(null); // create a Reference const handleDismiss = () => { toast.remove(id); }; useEffect(() => { timerID.current = setTimeout(() => { handleDismiss(); }, 4000); return () => { clearTimeout(timerID.current); }; }, []); return ( // JSX ); }; In the above code, we create a new timer using that will call the function after 4000 milliseconds (or 4 seconds). We then return a function from the hook that will clear the timer using the function when the component is unmounted. With these changes, our component will now automatically remove itself after 4 seconds. setTimeout handleDismiss cleanup useEffect clearTimeout Toast Toast Now let's add the progress bar to our Toast component. First, update the JSX of our component to include the progress bar like this: Toast const Toast = ({ message, type, id }) => { // code return ( <div className="toast"> <span className={iconClass}>{icon}</span> <p className="toast-message">{message}</p> <button className="dismiss-btn" onClick={handleDismiss}> <IconX size={18} color="#aeb0d7" /> </button> {/* Toast Progress Bar */} <div className="toast-progress"> <div className={`toast-progress-bar ${progressBarClass}`}></div> </div> </div> ); }; Next, we need to style and animate the progress bar using CSS: .toast-progress { position: absolute; bottom: 0; left: 0; width: 100%; height: 4px; background-color: rgba(0, 0, 0, 0.1); } .toast-progress-bar { height: 100%; animation: progress-bar 4s linear forwards; } .toast-progress-bar.success { background-color: var(--success); } .toast-progress-bar.info { background-color: var(--info); } .toast-progress-bar.warning { background-color: var(--warning); } .toast-progress-bar.error { background-color: var(--error); } @keyframes progress-bar { 0% { width: 100%; } 100% { width: 0%; } } In the above code, we define the progress bar style in the and CSS classes. .toast-progress .toast-progress-bar Additionally, we define four more CSS classes: , , , and . These classes define the background color of the progress bar based on the dynamic value in the component. .toast-progress-bar.success .toast-progress-bar.info .toast-progress-bar.warning .toast-progress-bar.error progressBarClass Toast We also use the rule to define the animation. This animation animates the width of the progress bar from 100% to 0% over 4 seconds. @keyframes progress-bar After applying these changes, our Toast component now displays an animated progress bar. Adding Hover on Pause functionality After adding the progress bar to our component, we can now further enhance its functionality by adding a pause on the hover feature. With this feature, users can pause the auto-dismiss timer and the progress bar animation by simply hovering their mouse over the Toast. Toast To add the hover-on pause functionality, we can use the and events in React. When the user hovers over the Toast, we can clear the auto-dismiss timer using the function to pause the timer. Then, when they move their mouse away, we can start a new timer with the remaining time. onMouseEnter onMouseLeave clearTimeout First, let's create a new reference called and attach it to the progress bar element to track whether the progress bar animation is currently paused or not. progressRef // Toast.js const progressRef = useRef(null); {/* Toast Progress Bar */} <div className="toast-progress"> <div ref={progressRef} className={`toast-progress-bar ${progressBarClass}`} ></div> </div> Next, we will create function to handle the event. When the mouse enters the toast, we will clear the timer using the and set the progress bar animation to to pause the animation. handleMouseEnter onMouseEnter clearTimeout paused // Toast.js const handleMouseEnter = () => { clearTimeout(timerID.current); progressRef.current.style.animationPlayState = "paused"; }; Similarly, we will create a function to handle the event. When the mouse leaves the toast, we will set the progress bar animation back to to resume the animation. handleMouseLeave onMouseLeave running const handleMouseLeave = () => { const remainingTime = (progressRef.current.offsetWidth / progressRef.current.parentElement.offsetWidth) * 4000; progressRef.current.style.animationPlayState = "running"; timerID.current = setTimeout(() => { handleDismiss(); }, remainingTime); }; In the above code, we first calculate the remaining time by dividing the current width of the progress bar by the total width of the progress bar container and multiplying it by the total duration (which is 4 seconds in our case). Next, we set the animation play state back to to resume the progress bar animation. Then, we create a new timer using and pass in the function as the callback, which will automatically dismiss the after the remaining time has passed. This ensures that the will still auto-dismiss even if the user pauses the animation for a certain period of time. running setTimeout handleDismiss Toast Toast Now we need to add these event listeners to the wrapper of the component. div Toast const Toast = ({ message, type, id }) => { // rest of the code return ( <div className="toast" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > <span className={iconClass}>{icon}</span> <p className="toast-message">{message}</p> <button className="dismiss-btn" onClick={handleDismiss}> <IconX size={18} color="#aeb0d7" /> </button> {/* Toast Progress Bar */} <div className="toast-progress"> <div ref={progressRef} className={`toast-progress-bar ${progressBarClass}`} ></div> </div> </div> ); }; With these changes, users can now hover over the to pause the animation and resume it when they move their mouse away. Toast Customizing Toast Notification Component position To customize the position of our Toast notification component, we can pass a different class as a prop to the component and add the corresponding CSS for that class. By default, our component is positioned at the top right of the screen using the class. position ToastsContainer ToastsContainer .toasts-container Let's first create a few more position classes in our CSS file and remove the default and property from the : top right .toasts-container .toasts-container { display: flex; flex-direction: column-reverse; row-gap: 12px; position: fixed; z-index: 9999; } .top-right { top: 16px; right: 16px; } .top-left { top: 16px; left: 16px; } .top-center { top: 16px; left: 50%; transform: translateX(-50%); } .bottom-left { bottom: 16px; left: 16px; } .bottom-center { bottom: 16px; left: 50%; transform: translateX(-50%); } .bottom-right { bottom: 16px; right: 16px; } Next, let's update our component to accept a prop and add that to the wrapper div: ToastsContainer position const ToastsContainer = ({ toasts, position = "top-right" }) => { return ( <div className={`toasts-container ${position}`}> {toasts.map((toast) => ( <Toast key={toast.id} {...toast} /> ))} </div> ); }; Now, when we use the component, we can pass a different prop to customize its position on the screen: ToastsContainer position // ToastContext.js <ToastContext.Provider value={value}> <ToastsContainer toasts={state.toasts} position="bottom-right" /> {children} </ToastContext.Provider> With these changes, we can customize the position of our Toast notifications by simply passing a class as a prop. position Adding animation to the Toast component Currently, our Toast notifications appear and disappear suddenly without any animation. In this section, we will add a slide-in and slide-out animation to the component using CSS. Toast To add a slide-in effect, we can use the rule to define an animation that gradually changes the of the Toast from to and translates it from to along the x-axis. We can then apply this animation to the class using the property in CSS. @keyframes opacity 0 1 100% 0% .toast animation /* App.css */ .toast { /* rest of the properties */ animation: slide-in 0.4s ease-in-out forwards; } @keyframes slide-in { 0% { opacity: 0; transform: translateX(100%); } 100% { opacity: 1; transform: translateX(0%); } } To add a slide-out effect, we can use a similar approach. We can define another animation using the rule that gradually changes the opacity of the Toast from 1 to 0 and translates it from to . @keyframes o% 100% /* App.css */ .toast-dismissed { animation: slide-out 0.4s ease-in-out forwards; } @keyframes slide-out { 0% { opacity: 1; transform: translateX(0%); } 100% { opacity: 0; transform: translateX(100%); } } To apply the class to the component when it is dismissed, we can create a new state variable called and set it to when the Toast is removed. Then, we can conditionally add the class to the component based on the value of . .toast-dismissed Toast dismissed true .toast-dismissed Toast dismissed // import useState hook import { useEffect, useRef, useState } from "react"; const Toast = ({ message, type, id }) => { // rest of the code const [dismissed, setDismissed] = useState(false); const handleDismiss = () => { setDismissed(true); setTimeout(() => { toast.remove(id); }, 400); }; return ( <div className={`toast ${dismissed ? "toast-dismissed" : ""}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > {/* rest of the code */} </div> ); }; In the above code, we have also updated the function slightly. Now, when the dismiss button is clicked, or the auto-dismiss timer is completed, the state variable is set to , and the class is added to the component. This will trigger the slide-out animation defined in the keyframe animation. Finally, after a short delay of 400ms, we remove the Toast component using the function. handleDismiss dismissed true .toast-dismissed Toast slide-out toast.remove() With these changes, we have added animations to our Toast component. When the Toast component appears, it slides in from the right of the screen, and when it is dismissed, it slides out in the same direction. Conclusion In this blog, we covered various aspects of building the Toast Notification component, including creating a context, creating a Toast component, and adding functionality such as an auto-dismiss timer, progress bar, pause on hover, and animation. We also learned how to customize the position of the Toast component using CSS. With this custom Toast notification component, you can easily add beautiful and informative notifications to your application. The possibilities for customization are endless, and you can tailor the component to your specific needs. We hope this blog has been helpful and informative, and we encourage you to try building your own custom Toast notification component using ReactJS and Context API. If you have any feedback or suggestions, please feel free to leave them in the comments section below. Also, don't forget to follow me on for more exciting content. Thank you for reading! Twitter Check out my open-source project, to take your frontend development skills to the next level for FREE! FrontendPro Also Published Here