├── .gitignore ├── README.md ├── images └── project_demo.gif ├── package-lock.json ├── package.json ├── public ├── 404.html ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── components ├── App.js ├── Dropdown.js ├── Header.js ├── Layout.js ├── MenuItems.js └── Navbar.js ├── index.js ├── menuItems.js └── routes ├── About.js ├── AboutWho.js ├── Frontend.js ├── Home.js ├── Node.js ├── OurValues.js ├── PHP.js ├── SEO.js ├── Services.js ├── WebDesign.js └── WebDev.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating a multilevel dropdown menu in React 2 | 3 | ## [Live project here](https://ibaslogic.github.io/react-multilevel-dropdown-menu/) 4 | 5 | Read the step-by-step guide here: https://blog.logrocket.com/creating-multilevel-dropdown-menu-react/ 6 | 7 | ## Demo here: 8 | 9 | ![React multilevel dropdown menu](./images/project_demo.gif) 10 | 11 | ## Clone project 12 | 13 | You can download or clone this project by running this command from your terminal: 14 | 15 | ``` 16 | git clone https://github.com/Ibaslogic/react-multilevel-dropdown-menu 17 | ``` 18 | 19 | Then: 20 | 21 | ``` 22 | cd react-multilevel-dropdown-menu 23 | npm install 24 | npm start 25 | ``` 26 | 27 | You should see the app in your browser address bar at [http://localhost:3000](http://localhost:3000) 28 | -------------------------------------------------------------------------------- /images/project_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ibaslogic/react-multilevel-dropdown-menu/bc44c1c234734d84ecee027279d95e600ec79a8d/images/project_demo.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-multilevel-dropdown-menu", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://ibaslogic.github.io/react-multilevel-dropdown-menu", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.3.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-router-dom": "^6.3.0", 13 | "react-scripts": "5.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "predeploy": "npm run build", 22 | "deploy": "gh-pages -d build" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "devDependencies": { 43 | "gh-pages": "^4.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ibaslogic/react-multilevel-dropdown-menu/bc44c1c234734d84ecee027279d95e600ec79a8d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 51 | 52 | 53 | 54 | 55 |
56 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ibaslogic/react-multilevel-dropdown-menu/bc44c1c234734d84ecee027279d95e600ec79a8d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ibaslogic/react-multilevel-dropdown-menu/bc44c1c234734d84ecee027279d95e600ec79a8d/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: sans-serif; 9 | } 10 | 11 | header { 12 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.07), 0 1px 2px 0 rgba(0, 0, 0, 0.05); 13 | color: #212529; 14 | } 15 | 16 | .nav-area { 17 | display: flex; 18 | align-items: center; 19 | max-width: 1200px; 20 | margin: 0 auto; 21 | padding: 10px 20px; 22 | } 23 | 24 | .logo { 25 | text-decoration: none; 26 | font-size: 25px; 27 | color: inherit; 28 | margin-right: 20px; 29 | } 30 | 31 | .menus { 32 | display: flex; 33 | align-items: center; 34 | flex-wrap: wrap; 35 | list-style: none; 36 | } 37 | 38 | .menu-items { 39 | position: relative; 40 | font-size: 14px; 41 | } 42 | 43 | .menu-items a { 44 | display: block; 45 | font-size: inherit; 46 | color: inherit; 47 | text-decoration: none; 48 | } 49 | 50 | .menu-items button { 51 | display: flex; 52 | align-items: center; 53 | color: inherit; 54 | font-size: inherit; 55 | border: none; 56 | background-color: transparent; 57 | cursor: pointer; 58 | width: 100%; 59 | } 60 | 61 | button span { 62 | margin-left: 3px; 63 | } 64 | 65 | .menu-items > a, .menu-items button { 66 | text-align: left; 67 | padding: 0.7rem 1rem; 68 | } 69 | 70 | .menu-items a:hover, 71 | .menu-items button:hover { 72 | background-color: #f2f2f2; 73 | } 74 | 75 | .arrow::after { 76 | content: ""; 77 | display: inline-block; 78 | margin-left: 0.28em; 79 | vertical-align: 0.09em; 80 | border-top: 0.42em solid; 81 | border-right: 0.32em solid transparent; 82 | border-left: 0.32em solid transparent; 83 | } 84 | 85 | .dropdown { 86 | position: absolute; 87 | right: 0; 88 | left: auto; 89 | box-shadow: 0 10px 15px -3px rgba(46, 41, 51, 0.08), 90 | 0 4px 6px -2px rgba(71, 63, 79, 0.16); 91 | font-size: 0.875rem; 92 | z-index: 9999; 93 | min-width: 10rem; 94 | padding: 0.5rem 0; 95 | list-style: none; 96 | background-color: #fff; 97 | border-radius: 0.5rem; 98 | display: none; 99 | } 100 | 101 | .dropdown.show { 102 | display: block; 103 | } 104 | 105 | .dropdown .dropdown-submenu { 106 | position: absolute; 107 | left: 100%; 108 | top: -7px; 109 | } 110 | 111 | /* content */ 112 | 113 | .content { 114 | max-width: 1200px; 115 | margin: 0 auto; 116 | padding: 3rem 20px; 117 | } 118 | 119 | .content h2 { 120 | margin-bottom: 1rem; 121 | } 122 | 123 | .content a { 124 | color: #cc3852; 125 | margin-right: 10px; 126 | } -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import { Routes, Route } from 'react-router-dom'; 2 | import Home from '../routes/Home'; 3 | import About from '../routes/About'; 4 | import WebDesign from '../routes/WebDesign'; 5 | import SEO from '../routes/SEO'; 6 | import Services from '../routes/Services'; 7 | import Layout from './Layout'; 8 | import Frontend from '../routes/Frontend'; 9 | import PHP from '../routes/PHP'; 10 | import Node from '../routes/Node'; 11 | import AboutWho from '../routes/AboutWho'; 12 | import OurValues from '../routes/OurValues'; 13 | import WebDev from '../routes/WebDev'; 14 | 15 | const App = () => { 16 | return ( 17 | <> 18 | 19 | }> 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | Not found!

} /> 32 |
33 |
34 | 35 | ); 36 | }; 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /src/components/Dropdown.js: -------------------------------------------------------------------------------- 1 | import MenuItems from './MenuItems'; 2 | const Dropdown = ({ submenus, dropdown, depthLevel }) => { 3 | depthLevel = depthLevel + 1; 4 | const dropdownClass = depthLevel > 1 ? 'dropdown-submenu' : ''; 5 | return ( 6 | 19 | ); 20 | }; 21 | 22 | export default Dropdown; 23 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import Navbar from './Navbar'; 2 | // ... 3 | import { Link } from 'react-router-dom'; 4 | 5 | const Header = () => { 6 | return ( 7 |
8 |
9 | 10 | Logo 11 | 12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default Header; 19 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import Header from './Header'; 3 | 4 | const Layout = () => { 5 | return ( 6 |
7 |
8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default Layout; 16 | -------------------------------------------------------------------------------- /src/components/MenuItems.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from 'react'; 2 | import Dropdown from './Dropdown'; 3 | 4 | import { Link } from 'react-router-dom'; 5 | 6 | const MenuItems = ({ items, depthLevel }) => { 7 | const [dropdown, setDropdown] = useState(false); 8 | 9 | let ref = useRef(); 10 | 11 | useEffect(() => { 12 | const handler = (event) => { 13 | if ( 14 | dropdown && 15 | ref.current && 16 | !ref.current.contains(event.target) 17 | ) { 18 | setDropdown(false); 19 | } 20 | }; 21 | document.addEventListener('mousedown', handler); 22 | document.addEventListener('touchstart', handler); 23 | return () => { 24 | // Cleanup the event listener 25 | document.removeEventListener('mousedown', handler); 26 | document.removeEventListener('touchstart', handler); 27 | }; 28 | }, [dropdown]); 29 | 30 | const onMouseEnter = () => { 31 | window.innerWidth > 960 && setDropdown(true); 32 | }; 33 | 34 | const onMouseLeave = () => { 35 | window.innerWidth > 960 && setDropdown(false); 36 | }; 37 | 38 | const closeDropdown = () => { 39 | dropdown && setDropdown(false); 40 | }; 41 | 42 | return ( 43 |
  • 50 | {items.url && items.submenu ? ( 51 | <> 52 | 72 | 77 | 78 | ) : !items.url && items.submenu ? ( 79 | <> 80 | 93 | 98 | 99 | ) : ( 100 | {items.title} 101 | )} 102 |
  • 103 | ); 104 | }; 105 | 106 | export default MenuItems; 107 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import { menuItems } from '../menuItems'; 2 | import MenuItems from './MenuItems'; 3 | const Navbar = () => { 4 | return ( 5 | 19 | ); 20 | }; 21 | 22 | export default Navbar; 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './components/App'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | 6 | // styles 7 | import './App.css'; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/menuItems.js: -------------------------------------------------------------------------------- 1 | export const menuItems = [ 2 | { 3 | title: 'Home', 4 | url: '/', 5 | }, 6 | { 7 | title: 'Services', 8 | url: '/services', 9 | submenu: [ 10 | { 11 | title: 'web design', 12 | url: 'web-design', 13 | }, 14 | { 15 | title: 'web development', 16 | url: 'web-dev', 17 | submenu: [ 18 | { 19 | title: 'Frontend', 20 | url: 'frontend', 21 | }, 22 | { 23 | title: 'Backend', 24 | submenu: [ 25 | { 26 | title: 'NodeJS', 27 | url: 'node', 28 | }, 29 | { 30 | title: 'PHP', 31 | url: 'php', 32 | }, 33 | ], 34 | }, 35 | ], 36 | }, 37 | { 38 | title: 'SEO', 39 | url: 'seo', 40 | }, 41 | ], 42 | }, 43 | { 44 | title: 'About', 45 | url: '/about', 46 | submenu: [ 47 | { 48 | title: 'Who we are', 49 | url: 'who-we-are', 50 | }, 51 | { 52 | title: 'Our values', 53 | url: 'our-values', 54 | }, 55 | ], 56 | }, 57 | ]; 58 | -------------------------------------------------------------------------------- /src/routes/About.js: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return

    About

    ; 3 | }; 4 | 5 | export default About; 6 | -------------------------------------------------------------------------------- /src/routes/AboutWho.js: -------------------------------------------------------------------------------- 1 | const AboutWho = () => { 2 | return

    Who we are

    ; 3 | }; 4 | 5 | export default AboutWho; 6 | -------------------------------------------------------------------------------- /src/routes/Frontend.js: -------------------------------------------------------------------------------- 1 | const Frontend = () => { 2 | return

    Frontend

    ; 3 | }; 4 | 5 | export default Frontend; 6 | -------------------------------------------------------------------------------- /src/routes/Home.js: -------------------------------------------------------------------------------- 1 | const Home = () => { 2 | return

    Home page content

    ; 3 | }; 4 | 5 | export default Home; 6 | -------------------------------------------------------------------------------- /src/routes/Node.js: -------------------------------------------------------------------------------- 1 | const Node = () => { 2 | return

    Node

    ; 3 | }; 4 | 5 | export default Node; 6 | -------------------------------------------------------------------------------- /src/routes/OurValues.js: -------------------------------------------------------------------------------- 1 | const OurValues = () => { 2 | return

    Our values

    ; 3 | }; 4 | 5 | export default OurValues; 6 | -------------------------------------------------------------------------------- /src/routes/PHP.js: -------------------------------------------------------------------------------- 1 | const PHP = () => { 2 | return

    PHP

    ; 3 | }; 4 | 5 | export default PHP; 6 | -------------------------------------------------------------------------------- /src/routes/SEO.js: -------------------------------------------------------------------------------- 1 | const SEO = () => { 2 | return

    Search engine optimization

    ; 3 | }; 4 | 5 | export default SEO; 6 | -------------------------------------------------------------------------------- /src/routes/Services.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | const Services = () => { 4 | return ( 5 | <> 6 |

    Services page

    7 | 8 | Web design 9 | Seo 10 | 11 | ); 12 | }; 13 | 14 | export default Services; 15 | -------------------------------------------------------------------------------- /src/routes/WebDesign.js: -------------------------------------------------------------------------------- 1 | const WebDesign = () => { 2 | return

    Web design content

    ; 3 | }; 4 | 5 | export default WebDesign; 6 | -------------------------------------------------------------------------------- /src/routes/WebDev.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const WebDev = () => { 4 | return

    Web development content

    ; 5 | }; 6 | 7 | export default WebDev; 8 | --------------------------------------------------------------------------------