No JavaScript Required
I find that web development is a bloated mess. There are too many tools and too many frameworks. I like to keep things simple, minimalist, and efficient.
This is a very technical blog post, and a basic understanding front-end development with HTML, CSS, and JS is recommended. With that disclosure out of the way, let’s jump in!
Bootstrap’s Navigation Bar and Burger Button
For this site I use Bootstrap for its styling CSS library. With it my website look good on mobile and desktop web browsers. It comes with useful built-in components, like the navigation bar that comes with a hamburger button. You can check out the demo for it by clicking here. However, it requires their JavaScript library to function. Without it the drop-down menu is permanently open on mobile.
I didn’t want to use Bootstrap’s 24 kilobytes of JavaScript code just to open and close a drop-down menu. So, I studied how the HTML was manipulated when I clicked the burger button:
After which, I made my own little script (which is less then 500 bytes in size) to do the same. Just without the opening/closing animation; the menu just shows or hides when the button is clicked. You can check this out in my demo by clicking here.
JavaScript
const divNavBar = document.getElementById("navbarNav");
const divNavBarButton = document.getElementById("navbar-toggler-button");
divNavBarButton.addEventListener("click", (event) =>
{
divNavBar.classList.toggle("show");
divNavBarButton.classList.toggle("collapsed");
const isAriaExpanded = ((divNavBarButton.getAttribute("aria-expanded") === "true")
? "false"
: "true");
divNavBarButton.setAttribute("aria-expanded", isAriaExpanded);
}
);
Then, I pondered… can I get rid of JavaScript entirely?
Creating a functioning drop-down navigation menu with a hamburger button without JavaScript
Searching online provides us with various guides for creating drop-down buttons with CSS only, no JavaScript required.
- How to make menus with CSS—no JavaScript or Bootstrap required! by Lee Warrick of Strings and Things
- Create a Responsive Hamburger Menu with HTML and CSS: A Step-by-Step Tutorial by Mukund Kumra
- Burger navigation menu with no JavaScript and no checkbox hack by Lucas Levin
The trick is to use HTML elements that have an on click state change that can selected with CSS and to use CSS combinators (namely +
or ~
) to control visibility of the drop-down menu on the aforementioned state change.
- A <button> can have a pointer hover over it or it can be in focus.
button:hover
button:focus
- A <checkbox> can be checked
input:checked
- A <details> can be open
details[open]
Hover CSS pseudo selector
You could use the :hover
CSS pseudo-class to get a drop-down menu to open when you mouse over the button with a cursor. The problem is, it does NOT work on mobile since you can’t hover with your finger.
Focus CSS pseudo selector
Some HTML elements, such as a button or a link, are focusable. An elements is in focus if it is clicked, touched, or tabbed (keyboard selected) into. We can use that to show/hide the navigation drop-down menu given the :focus
CSS pseudo-class of the button. But we also need to keep the menu open while we focus on options inside the menu. For that we want to use the :focus-within
CSS pseudo-class. Check this out in action in my demo by clicking here.
HTML
<header>
<nav class="navbar navbar-expand-sm bg-body-secondary">
<div class="container-sm">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler"
id="navbar-toggler-button"
type="button"
aria-label="Opened navigation menu"
aria-expanded="true"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse text-end show" id="navbarNav">
<div class="navbar-nav">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">Blog</a>
<a class="nav-link" href="#">Subscribe</a>
<a class="nav-link" href="#">About</a>
</div>
</div>
</div>
</nav>
</header>
The HTML markup is mostly unchanged from the original.
CSS
.navbar #navbarNav {
display: none;
}
.navbar:focus-within #navbarNav {
display: block;
}
The CSS is quite simple: hide the menu normally, but when anything in the navigation bar is focused, like the burger button or the menu item links themselves, do show (display: block;
). This works on mobile, and this works with screen readers.
The downside of this approach is you cannot close the menu by clicking the burger button. You have to click outside of the navigation bar for it to close.
Checkbox
We can replace the button with a hidden checkbox input and a label with the burger icon showing. Using the checkbox state of :checked
in CSS we can show or hide the menu when ever the icon is clicked. This visually works exactly the same as using JavaScript. You can check this out in my demo by clicking here.
HTML
<header>
<nav class="navbar navbar-expand-sm bg-body-secondary">
<div class="container-sm">
<a class="navbar-brand" href="#">Navbar</a>
<label for="burger-checkbox"
class="navbar-toggler"
aria-label="Toggle navigation"
aria-controls="navbarNav"
>
<span class="navbar-toggler-icon"></span>
</label>
<input type="checkbox" id="burger-checkbox">
<div class="collapse navbar-collapse text-end" id="navbarNav">
<div class="navbar-nav">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">Blog</a>
<a class="nav-link" href="#">Subscribe</a>
<a class="nav-link" href="#">About</a>
</div>
</div>
</div>
</nav>
</header>
Notice the icon within the <label>
tag, and the <input>
tag is after it.
CSS
/* Hide the checkbox */
input#burger-checkbox {
display: none;
}
/* On close, hide options */
input#burger-checkbox + div {
display: none;
}
/* On open, show options */
input#burger-checkbox:checked + div {
display: block;
}
The checkbox label (which contains our burger icon) toggles the checkbox input (thanks to for="burger-checkbox"
attribute) when clicked. Thanks to that we can just hide the checkbox, and indirectly set it to be checked or not.
The CSS works as follows:
- Hide the div (that contains the drop-down navigation menu) that’s after the unchecked checkbox.
- Show div that’s after the checked checkbox.
The main downside with this approach is that it trips up screen readers. For accessibility it’s not great, as it’s not clear that this checkbox expands a menu. Even when using ARIA attributes the semantics that some drop-down menu is expanded or collapsed is lost.
Details (disclosure widget)
There exists a HTML element that shows hidden content with no JavaScript and no CSS trickery. The <details>
element and its child element <summary>
when clicked will present the remaining child contents in <details>
. This is also sometimes called the disclosure widget.
If you click here, on this "summary"...
You will reveal hidden content. This is the default behaviour, no JavaScript code was written to have this shown.Screen reader pick up on these elements, and disclose via voice whether the details element is expanded or collapsed.
I found there are two ways of using this to create a code-free burger menu.
Option 1: Drop-down menu inside the details element
Let’s take advantage of the way details shows/hides when it is expanded/collapsed by putting the menu of navigation links inside of it. You can check out the demo by clicking here.
HTML
<header>
<nav class="navbar navbar-expand-sm bg-body-secondary">
<!-- Hide when window gets small -->
<div class="container-sm" id="hide-on-xs-fix">
<a class="navbar-brand" href="#">NavbarA</a>
<div class="text-end">
<div class="navbar-nav">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">Blog</a>
<a class="nav-link" href="#">Subscribe</a>
<a class="nav-link" href="#">About</a>
</div>
</div>
</div>
<!-- Show the burger button when window gets small -->
<details class="container-sm d-block d-sm-none">
<summary class="burger-summary d-flex justify-content-between"
aria-label="Toggle navigation"
>
<a class="navbar-brand" href="#">NavbarB</a>
<div class="navbar-toggler">
<span class="navbar-toggler-icon"></span>
</div>
</summary>
<div class="collapse navbar-collapse show" id="navbarNav">
<div class="navbar-nav text-end">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">Blog</a>
<a class="nav-link" href="#">Subscribe</a>
<a class="nav-link" href="#">About</a>
</div>
</div>
</details>
</nav>
</header>
You may notice there are two set of links for navigation, one for bigger screens and another for the burger menu. This is required for this approach as you can’t reveal items in a disclosure widget via CSS selectors. Thus, if you want to have separate navigation links shown for larger screens, you will need two sets. Which is a bit of a downside.
CSS
/* Removes the summary triangle */
summary.burger-summary {
display: block;
}
/* Removes the summary triangle for safari */
summary.burger-summary::-webkit-details-marker {
display:none;
}
The CSS is simple, as we don’t have to add selectors to hide anything. We only need to remove the summary triangle ▹.
This works okay when using screen readers. Except on Microsoft’s version called Narrator, where for some reason it can’t focus on the summary element to click on. I think with a bit of finesse this implementation could be adjusted to work better with screen readers.
Option 2: Using the “open” attribute of details element
Similar to the checkbox method, we can apply a CSS selector to show/hide the menu given the state of <details>
of having the open attribute or not. You can check this out in my demo by clicking here.
HTML
<header>
<nav class="navbar navbar-expand-sm bg-body-secondary">
<div class="container-sm">
<a class="navbar-brand" href="#">Navbar</a>
<details class="navbar-toggle-details">
<summary class="burger-summary navbar-toggler"
aria-label="Toggle navigation"
aria-controls="navbarNav"
>
<span class="navbar-toggler-icon"></span>
</summary>
</details>
<div class="collapse navbar-collapse text-end" id="navbarNav">
<div class="navbar-nav">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">Blog</a>
<a class="nav-link" href="#">Subscribe</a>
<a class="nav-link" href="#">About</a>
</div>
</div>
</div>
</nav>
</header>
This time, only one set of links is required.
/* Removes the summary drop down arrow */
summary.burger-summary {
display: block;
}
/* Removes the summary drop down arrow for safari*/
summary.burger-summary::-webkit-details-marker {
display:none;
}
/* On close, hide options */
details.navbar-toggle-details + div {
display: none;
}
/* On open, show options */
details.navbar-toggle-details[open] + div {
display: block;
}
This is very similar to using checkbox + label elements. Except, unlike the checkbox method it is supportive of accessibility needs. Screen readers pick up the semantics of when the navigation menu is collapsed or expanded on click. Although, I will note that on MacOs’s VoiceOver, when using Safari, when clicking on the summary element, it loses the focus to somewhere random. This issue does not happen when using Firefox or Chrome on MacOs.
Accessibility
I think making your site accessible to those with disabilities is a worthy endeavour. As such, I have been playing around with screen readers to test out how different HTML elements with (and without) ARIA (Accessible Rich Internet Applications) properties affect control and speech of my JavaScript free hamburger buttons.
The linked demos are my best effort in keeping each method screen reader friendly, while also not using JavaScript.
Here’s a general ranking of how well each strategy does according to my testing:
- Focus pseudo selector. This one is the only method that passes the tabbing test, where hitting tab will cycle through all the navigation links, without needing to action off any button.
- JavaScript! With appropriate change of
aria-expanded
attribute on click, it works well. - Details, using open attribute. Acts the same as the JavaScript coded button, except on Safari when using VoiceOver.
- Details, with menu inside. It’s works poorly with Microsoft’s Narrator okay with other screen readers.
- Checkbox. Can be clicked, but is unclear that it opens a drop-down navigation menu.
- hover. Broken, does not work, do not use.
Conclusion
You don’t need JavaScript on a static website like this one. I demonstrated a couple of ways to have the quintessential burger button open a dropdown navigation menu, while also keeping it accessible to screen readers, with no JS code.
Even web forms can work without JavaScript. If I set it up correctly, and you disable JavaScript on your browser, you will still be able to submit your email to subscribe to this blog.
Credits
Written by Victor Efimov
Published on January 31, 2025