DOWNLOAD JSON ZIPPED Theme header
Hamburger menu desktop mobile is the goal of this guide. You want one menu. It should look slick on desktop. It should feel smooth on mobile. This tutorial shows how to do that in Divi with a shortcode, Bootstrap 5, and lightweight CSS.
In my video, I walk through the full setup. The code is below that video on the article. You’ll paste it where noted. Then you’ll style it in Divi. No extra heavy plugins are needed.
What You’ll Build
You’ll add a toggle button that opens a right-side offcanvas panel. The panel holds your main WordPress menu. On desktop, submenus open on click. On mobile, submenus use simple +/– toggles. It’s fast, clean, and easy to edit.
This is the hamburger menu desktop mobile pattern done right. One layout. Two behaviors tailored to screen size.
How It Works (High Level)
-
Shortcode for the menu.
The PHP snippet registers[hamburger]
. It outputs your “hamburger” menu from WordPress Menus. You can rename the menu if yours has a different title. -
Bootstrap offcanvas layout.
The HTML adds the toggle button and the offcanvas panel. You drop this in your Divi Theme Builder header. The shortcode sits inside the panel and prints your menu items. -
Desktop and mobile logic.
The JS uses Bootstrap 5 for the offcanvas. It also adds click-to-open submenus on desktop widths. On mobile, the script enhances the default Divi mobile menu with +/– toggles. -
Polished CSS.
The CSS styles the panel, links, nested lists, and social icons. It also handles spacing, hover states, and scroll behavior. The result is professional and consistent.
Where to Paste Each Piece
-
PHP → Use the Code Snippets plugin. Activate the snippet site-wide.
-
Bootstrap link + script + JS → Add in Divi Integration > Head.
-
Offcanvas HTML → Add to your Divi Theme Builder header using a Code or Text module. Place
[hamburger]
inside the offcanvas body. -
CSS → Add in Divi Theme Options > Custom CSS or your child theme.
Setup Tips
-
In Appearance > Menus, create a menu named hamburger. Or update the snippet to match your menu name.
-
Keep labels short. It avoids wrapping in narrow panels.
-
Test nested levels. The CSS already indents deeper submenus.
-
Add your social icons right inside the offcanvas. Links are included in the code.
Why This Pattern
The hamburger menu desktop mobile approach gives you one code path. It avoids maintaining separate headers. Bootstrap handles the offcanvas mechanics. Your shortcode keeps the menu dynamic. Divi’s Theme Builder makes placement simple.
Troubleshooting
-
Offcanvas won’t open? Make sure the Bootstrap CSS and bundle JS are loaded once.
-
Submenus not toggling on desktop? Confirm the custom JS is in place and widths are above 980px.
-
Mobile menu overlapping content? Keep the provided CSS for fixed positioning and scroll.
-
Icons not showing? Ensure Font Awesome is loaded, or switch to Divi icons.
Helpful Resources (Outbound)
-
Divi Theme by Elegant Themes: https://www.elegantthemes.com/gallery/divi/
-
Bootstrap 5 Offcanvas Docs: https://getbootstrap.com/docs/5.3/components/offcanvas/
-
WordPress Menus Guide: https://wordpress.org/support/article/appearance-menus-screen/
-
Font Awesome Icons: https://fontawesome.com
-
Code Snippets Plugin: https://wordpress.org/plugins/code-snippets/
PHP CODE (Place in new code on code snippet plugin)
function main_menu_shortcode() { ob_start(); wp_nav_menu([ 'menu' => 'hamburger', // ✅ Change this if your menu has a different name 'container' => false, 'menu_class' => 'nav flex-column', 'fallback_cb' => false, 'items_wrap' => '<ul class="%2$s">%3$s</ul>', ]); return ob_get_clean(); } add_shortcode('hamburger', 'main_menu_shortcode');
Bootstrap html code (place on menu in theme builder
<!-- Menu Toggle Button --> <button class="menu-toggle-btn ms-auto" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" aria-label="Toggle navigation"> <svg class="menu-icon" viewBox="0 0 100 100" width="50" height="50"> <rect y="20" width="100" height="7" rx="3"></rect> <rect y="45" width="100" height="7" rx="3"></rect> <rect y="70" width="100" height="7" rx="3"></rect> </svg> </button> <!-- Offcanvas Sidebar Menu --> <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel"> <div class="offcanvas-header"> <h5 class="offcanvas-title" id="offcanvasNavbarLabel"></h5> <button class="custom-close-btn" type="button" data-bs-dismiss="offcanvas" aria-label="Close"> <i class="fas fa-times"></i> <!-- Font Awesome close icon --> </button> </div> <div class="offcanvas-body"> <div class="offcanvas-inner"> [hamburger] <!-- ✅ This pulls your main menu --> </div> <!-- End offcanvas-inner --> </div> </div>
JS CODE (Place on integration – Head)
<!-- ✅ Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <!-- ✅ Bootstrap 5 Bundle JS (required for off-canvas) --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script> document.addEventListener("DOMContentLoaded", function () { /* ====================================================== DESKTOP MENU — CLICK TO OPEN (only when > 980 px wide) ====================================================== */ const nav = document.querySelector("#offcanvasNavbar .offcanvas-body"); if (nav && window.innerWidth > 980) { const dropdowns = nav.querySelectorAll(".menu-item-has-children"); dropdowns.forEach(item => { const trigger = item.querySelector("a"); if (trigger) { trigger.addEventListener("click", function (e) { e.preventDefault(); const submenu = item.querySelector(".sub-menu"); /* close *only* siblings at the same depth (skip ancestors & descendants) */ dropdowns.forEach(other => { if ( other !== item && !item.contains(other) && // other is not inside item !other.contains(item) // item is not inside other ) { other.classList.remove("open"); const otherSub = other.querySelector(".sub-menu"); if (otherSub) otherSub.style.display = "none"; } }); /* toggle the one that was clicked */ item.classList.toggle("open"); if (submenu) { submenu.style.display = item.classList.contains("open") ? "block" : "none"; } }); } }); } /* ====================================================== MOBILE MENU (unchanged) ====================================================== */ function initMobileMenuToggles() { const menu = document.querySelector("ul.et_mobile_menu"); if (!menu) return; const dropdownItems = menu.querySelectorAll( "li.menu-item-has-children, li.page_item_has_children" ); dropdownItems.forEach(item => { if (!item.querySelector(".mobile-toggle")) { const toggle = document.createElement("a"); toggle.href = "#"; toggle.className = "mobile-toggle"; toggle.textContent = "+"; item.appendChild(toggle); toggle.addEventListener("click", function (e) { e.preventDefault(); item.classList.toggle("dt-open"); const submenu = item.querySelector("ul.children, ul.sub-menu"); if (submenu) submenu.classList.toggle("visible"); toggle.textContent = item.classList.contains("dt-open") ? "-" : "+"; }); } }); } const hamburger = document.querySelector(".mobile_menu_bar"); if (hamburger) { hamburger.addEventListener("click", function () { document.body.classList.toggle("menu-opened"); if (document.body.classList.contains("menu-opened")) { setTimeout(initMobileMenuToggles, 100); } }); } }); </script>
Menu social code
<a href="https://facebook.com/yourpage" target="_blank"><i class="fab fa-facebook-f"></i></a> <a href="https://instagram.com/yourusername" target="_blank"><i class="fab fa-instagram"></i></a> <a href="https://youtube.com/yourchannel" target="_blank"><i class="fab fa-twitter"></i></a>
CSS CODE
/* =================================================================== TABLE OF CONTENTS ------------------------------------------------------------------- 1. General Offcanvas Menu Styling 1.1. Offcanvas Container & Layout 1.2. Offcanvas Header 1.3. Offcanvas Body & Inner Content 1.4. Offcanvas Width Settings 2. Menu Toggle Button (Hamburger Icon) 3. General Menu Link Styling 3.1. Main Menu Links 3.2. Submenu Links 3.3. Hover States 3.4. List Reset 4. Dropdown Menus (Hover & Click-Based Behavior) 4.1. Parent Item Styling 4.2. Arrow Symbols 4.3. Submenu Container Styling 4.4. Submenu Indentation 4.5. Submenu Visibility (JS Controlled) 5. Social Icons 5.1. Offcanvas Social Icons 5.2. Mobile Menu Social Icons 6. Mobile Menu Styling (Divi Specific) 6.1. Mobile Menu Container 6.2. Mobile Menu Links 6.3. Mobile Menu Toggle Icon (+/-) 6.4. Mobile Submenu Visibility 6.5. Mobile Submenu Link Styling 7. Divi Specific Fixes =================================================================== */ /* Define a grayish-black color variable for consistency */ :root { grayish-black: #333333; /* A dark gray for hover effects */ } /* =================================================================== 1. GENERAL OFFCANVAS MENU STYLING =================================================================== */ /* 1.1. Offcanvas Container & Layout */ #offcanvasNavbar.offcanvas-end { width: 650px; max-width: 90%; } /* 1.2. Offcanvas Header */ #offcanvasNavbar .offcanvas-header { padding-top: 50px; padding-bottom: 10px; display: flex; align-items: center; justify-content: space-between; } #offcanvasNavbar .offcanvas-title, #offcanvasNavbarLabel { color: black; /* Changed to black */ font-size: 40px; text-align: left; } #offcanvasNavbar .custom-close-btn { font-size: 40px; font-weight: thin; color: black; /* Changed to black */ background: none; border: none; } #offcanvasNavbar .custom-close-btn:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* 1.3. Offcanvas Body & Inner Content */ #offcanvasNavbar .offcanvas-body { padding: 0; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-start; } #offcanvasNavbar .offcanvas-inner { height: 100vh; overflow-y: auto; -ms-overflow-style: none; scrollbar-width: none; padding-left: 25px; padding-right: 25px; } #offcanvasNavbar .offcanvas-inner::-webkit-scrollbar { width: 0px; background: transparent; display: none; } /* 1.4. Offcanvas Width Settings */ @media (max-width: 767px) { #offcanvasNavbar.offcanvas-end { width: 100vw; max-width: 100vw; } } /* =================================================================== 2. MENU TOGGLE BUTTON (Hamburger Icon) =================================================================== */ .menu-toggle-btn { background: none; border: none; padding: 0; } .menu-toggle-btn svg rect { fill: black; /* Changed to black */ transition: fill 0.3s; } .menu-toggle-btn:hover svg rect { fill: var(--grayish-black); /* Changed to grayish-black */ } /* =================================================================== 3. GENERAL MENU LINK STYLING =================================================================== */ /* 3.1. Main Menu Links */ #offcanvasNavbar .offcanvas-body .menu-item a { font-size: 24px; font-weight: 700; color: black; /* Changed to black */ padding: 10px 0; margin-bottom: 4px; display: block; text-decoration: none !important; text-align: left; transition: color 0.3s; width: 100%; background: transparent !important; } /* Parent items stay black by default */ #offcanvasNavbar .offcanvas-body .menu-item > a { color: black; /* Remains black */ } /* 3.2. Submenu Links */ #offcanvasNavbar .offcanvas-body .sub-menu a { padding: 0.5px; margin: 1.5px; line-height: 1.3; font-weight: 600; font-size: 22px; color: black; /* Changed to black */ } /* Level-2 (grand-child menu) */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu a { color: black; /* Remains black */ font-size: 20px; } /* Level-3 (great-grand-child) — optional */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu .sub-menu a { color: black; /* Changed to black */ font-size: 20px; } /* Level-4 (rare) */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu .sub-menu .sub-menu a { color: black; /* Remains black */ } /* 3.3. Hover States */ /* Parent hover */ #offcanvasNavbar .offcanvas-body .menu-item > a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* Submenu hover */ #offcanvasNavbar .offcanvas-body .sub-menu a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* Level-2 hover */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* Level-3 hover */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu .sub-menu a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* Level-4 hover */ #offcanvasNavbar .offcanvas-body .sub-menu .sub-menu .sub-menu .sub-menu a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* 3.4. List Reset */ /* Remove bullets and default padding/margin from lists */ #offcanvasNavbar .offcanvas-body ul, #offcanvasNavbar .offcanvas-body li { list-style: none; margin: 0; padding: 0; } /* =================================================================== 4. DROPDOWN MENUS (Hover & Click-Based Behavior) =================================================================== */ /* 4.1. Parent Item Styling */ /* Parent must be relative to anchor submenu positioning */ #offcanvasNavbar .offcanvas-body .menu-item-has-children { position: relative; } #offcanvasNavbar .offcanvas-body .menu-item-has-children > a { position: relative; padding-right: 30px; } /* 4.2. Arrow Symbols */ /* Arrow symbol for parent items */ #offcanvasNavbar .offcanvas-body .menu-item-has-children > a::after { content: "⌵"; position: absolute; right: 0; top: 50%; transform: translateY(-50%); font-size: 24px; color: black; /* Changed to black */ transition: transform 0.3s ease; } /* Arrow: default down, rotate only when OPEN */ #offcanvasNavbar .offcanvas-body .menu-item-has-children > a::after { transform: translateY(-50%); } #offcanvasNavbar .offcanvas-body .menu-item-has-children.open > a::after { transform: translateY(-50%) rotate(90deg); } /* Nested levels use + / – instead of the arrow */ #offcanvasNavbar .offcanvas-body .sub-menu .menu-item-has-children > a::after { content: "+"; /* plus when closed */ font-size: 30px; color: black; /* Changed to black */ transform: translateY(-50%); /* keep it centred; no rotation */ } #offcanvasNavbar .offcanvas-body .sub-menu .menu-item-has-children.open > a::after { content: "–"; /* minus when open */ font-size: 30px; } /* 4.3. Submenu Container Styling */ /* Submenu base: dropdown without layout shift */ #offcanvasNavbar .offcanvas-body ul.sub-menu { display: none; opacity: 0; visibility: hidden; position: absolute !important; /* overlay for first load */ top: 100%; left: 0; z-index: 999; padding: 0 !important; margin: 0 !important; width: 100% !important; background: white; /* Remains white */ box-shadow: none !important; transition: opacity 0.3s ease; } /* Make level-2 containers flow like level-1 */ #offcanvasNavbar .offcanvas-body ul.sub-menu ul.sub-menu { padding-left: 0 !important; margin-left: 0 !important; width: 100% !important; position: static !important; /* behaves like first level */ background: white; /* Remains white */ box-shadow: none !important; } /* 4.4. Submenu Indentation */ /* First-level submenu: indent visually 5px */ #offcanvasNavbar .offcanvas-body .menu-item-has-children > .sub-menu { margin-left: 5px !important; padding-left: 5px !important; width: calc(100% - 5px) !important; } /* Second-level submenu: indent 20px */ #offcanvasNavbar .offcanvas-body ul.sub-menu > .menu-item-has-children > .sub-menu { margin-left: 20px !important; padding-left: 10px !important; width: calc(100% - 20px) !important; } /* Third-level submenu: indent 30px */ #offcanvasNavbar .offcanvas-body ul.sub-menu ul.sub-menu > .menu-item-has-children > .sub-menu { margin-left: 30px !important; padding-left: 10px !important; width: calc(100% - 30px) !important; } /* Fourth-level submenu: indent 40px */ #offcanvasNavbar .offcanvas-body ul.sub-menu ul.sub-menu ul.sub-menu > .menu-item-has-children > .sub-menu { margin-left: 40px !important; padding-left: 10px !important; width: calc(100% - 40px) !important; } /* 4.5. Submenu Visibility (JS Controlled) */ /* Hide submenus by default */ #offcanvasNavbar .offcanvas-body .menu-item-has-children > .sub-menu { display: none; opacity: 1 !important; visibility: visible !important; position: static !important; margin-top: 10px; } /* Base state: submenu hidden */ #offcanvasNavbar .offcanvas-body .menu-item-has-children > .sub-menu { display: none; } /* Show submenu only when parent has class 'open' (set via JS) */ /* Keep every level in normal flow when open */ #offcanvasNavbar .offcanvas-body .menu-item-has-children.open > .sub-menu { display: block; position: static !important; } /* Clicked state: submenu visible */ #offcanvasNavbar .offcanvas-body .menu-item-has-children.open > .sub-menu { display: block; } /* =================================================================== 5. SOCIAL ICONS =================================================================== */ /* 5.1. Offcanvas Social Icons */ #offcanvasNavbar .offcanvas-social { text-align: left; margin-top: 15px !important; margin-bottom: 20px; } #offcanvasNavbar .offcanvas-social a { color: black; /* Changed to black */ font-size: 24px; margin-right: 12px; transition: color 0.3s ease; } #offcanvasNavbar .offcanvas-social a:hover { color: var(--grayish-black); /* Changed to grayish-black */ } /* 5.2. Mobile Menu Social Icons */ @media only screen and (max-width: 980px) { .et_mobile_menu .menu-item.social-icons { display: flex; gap: 10px; justify-content: center; align-items: center; width: 100%; } .et_mobile_menu .menu-item.social-icons a { display: inline-flex; align-items: center; font-size: 20px; color: white !important; /* Retained white for mobile social icons */ } } .et_mobile_menu .menu-item.socials { display: flex; justify-content: flex-start; align-items: left; gap: 10px; padding: 10px 15px 25px 25px; /* Matches Divi menu padding */ width: 100%; margin: 20px; margin-left: -63px; box-sizing: border-box; } .et_mobile_menu .menu-item.socials a { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; background: white; /* Remains white */ border-radius: 50%; font-size: 30px; color: black !important; /* Changed to black */ text-decoration: none !important; transition: background 0.3s ease; margin: 0; /* Remove any browser default margin */ padding: 0; } /* =================================================================== 6. MOBILE MENU STYLING (Divi Specific) =================================================================== */ /* 6.1. Mobile Menu Container */ .et_mobile_menu { width: 100% !important; max-width: 100vw !important; left: 0 !important; right: 0 !important; position: fixed !important; top: 90px !important; z-index: 9999; background-color: black; /* Changed to black */ padding-top: 20px; box-sizing: border-box; display: none; overflow-y: auto; max-height: calc(100vh - 80px); } body.menu-opened { overflow-x: hidden; } .menu-opened .et_mobile_menu { display: block; } .et_mobile_menu ul { display: flex; flex-direction: column; align-items: center; width: 100%; padding: 0; } .et_pb_menu_0.et_pb_menu .et_mobile_menu, .et_pb_menu_0.et_pb_menu .et_mobile_menu ul { background-color: white !important; /* Remains white */ border-radius: 10px; } /* 6.2. Mobile Menu Links */ .et_mobile_menu a { text-decoration: none !important; } .et_mobile_menu a, .et_mobile_menu .menu-item-has-children > a { background-color: white !important; /* Remains white */ color: black !important; /* Remains black */ } ul.et_mobile_menu > li.menu-item-has-children, ul.et_mobile_menu > li.page_item_has_children { position: relative; } ul.et_mobile_menu li.menu-item-has-children > a::after, ul.et_mobile_menu li.page_item_has_children > a::after { content: none !important; } /* 6.3. Mobile Menu Toggle Icon (+/-) */ /* Style the mobile menu icon (hamburger and X) */ .mobile_menu_bar { font-size: 40px; color: black; /* Changed to black */ cursor: pointer; position: fixed; top: 20px; right: 2px; z-index: 10001; } /* Use ETModules icon font for mobile toggle */ .mobile_menu_bar::before { content: ''; /* Hamburger */ font-family: 'ETModules'; transition: content 0.3s ease; } .menu-opened .mobile_menu_bar::before { content: 'd'; /* Close (X) */ color: black !important; /* Changed to black */ } ul.et_mobile_menu li.menu-item-has-children .mobile-toggle, ul.et_mobile_menu li.page_item_has_children .mobile-toggle { width: 44px; height: 100%; padding: 0 !important; max-height: 44px; border: none; position: absolute; right: 0; top: 16px; z-index: 999; background-color: transparent; font-family: Arial, sans-serif; font-size: 30px; color: black !important; /* Changed to black */ display: inline-block; text-align: center; opacity: 1; } /* 6.4. Mobile Submenu Visibility */ ul.et_mobile_menu .menu-item-has-children .sub-menu { display: none !important; visibility: hidden !important; } ul.et_mobile_menu .menu-item-has-children .sub-menu.visible { display: block !important; visibility: visible !important; } /* Allow nested submenus (2nd level, 3rd level, etc.) to be visible when toggled */ ul.et_mobile_menu .sub-menu .sub-menu { display: none !important; visibility: hidden !important; } ul.et_mobile_menu .sub-menu.visible .sub-menu { display: block !important; visibility: visible !important; } /* 6.5. Mobile Submenu Link Styling */ /* Keep submenu links styled consistently */ ul.et_mobile_menu .sub-menu a { background-color: white !important; /* Remains white */ color: black !important; /* Remains black */ padding-left: 4px; /* indent sub-levels */ font-weight: normal; } /* Optional: Add more indent for deeper levels */ ul.et_mobile_menu .sub-menu .sub-menu a { padding-left: 10px; } ul.et_mobile_menu .sub-menu .sub-menu .sub-menu a { padding-left: 20px; } /* =================================================================== 7. DIVI SPECIFIC FIXES =================================================================== */ /* Ensures Divi buttons maintain text-decoration */ .et_pb_button, .et_pb_promo_button, .et_pb_more_button, button.et_pb_button, a.et_pb_button { text-decoration: none !important; } /* Removes default borders/shadows from offcanvas submenus */ #offcanvasNavbar .offcanvas-body ul.sub-menu { border: none !important; border-radius: 0; box-shadow: none !important; } /* Divi specific: Superscript font size */ .et_pb_text sup { font-size: 75%; }