A hamburger menu is a navigation UI commonly used on mobile websites. When the “≡” button in the header is pressed, a list of links appears over the current page.
The three-line button resembles a hamburger, with buns and a patty stacked together, so it is commonly called a “hamburger menu.”
There are many old and new ways to build one, but for current implementations, using the HTML <dialog> element is recommended. Combined with CSS features that have become available in the 2020s, it can be implemented cleanly.
Why older implementations are worth avoiding
Hamburger menus have been widely used on websites since around the late 2000s, when mobile devices began to spread. Let’s look back at the issues older implementations had. You may even find cases where one was used without realizing it 😨.
A div that only looks like a menu
Examples of hamburger menus have long used code that adds and removes classes on a <div> element to switch visibility. For example, the visible state may be controlled only by a class name.
<!-- Manage visibility with a class name -->
<button class="menu-button">Menu</button>
<div class="menu is-open">
<a href="#">Work</a>
</div>
One disadvantage of using a <div> element is that when the menu is open, pressing the Tab key can still move focus to links and forms behind it.
Even though the creator did not intend those background elements to be operable, users can still reach them in an unintended way, which can lead to unexpected problems.
The checkbox hack
Another approach uses a checkbox with an <input> element and a <label> element to switch visibility. Its advantage is that it works without JavaScript.
For example, the checked state is used as the menu’s visible state.
<!-- Toggle the menu with the checked state -->
<input id="menu-toggle" type="checkbox" />
<label for="menu-toggle">Menu</label>
<nav class="menu">...</nav>
However, this is not recommended as a way to open and close navigation. A checkbox is a form control, so assistive technologies announce it as a checkbox, not as “a button that opens a menu.”
Benefits of the dialog element
This is where the <dialog> element is useful. The <dialog> element includes standard features for building modal UIs.
Benefits:
- It can be shown in the top layer with
showModal(), so there is no need to adjustz-index. - While the hamburger menu is open, the Tab key does not move focus to the background.
- The standard behavior for closing with the Escape key can be used.
- The dark backdrop layer can be styled with the
::backdroppseudo-element. - It is announced to screen readers as a dialog.
- It can be implemented with concise code.
The focus trap works, so focus does not move to the underlying layer.
Full-screen menus and drawers are layers that sit on top of the current page, so the <dialog> element works well as a modal UI. The name <dialog> often makes people think of UI such as alert dialogs, but it is worth remembering that the <dialog> element can be used for a wide range of modal UIs.
The basics of the <dialog> element and browser-specific behavior are covered in detail in HTML dialog element for modal UIs.
Basic implementation with the dialog element
Let’s look at a typical hamburger menu implementation. The following demo can be operated with the “≡” button in the upper-right corner.
The HTML has the following structure:
- an open button in the header
- a close button inside the
<dialog>element - navigation inside the
<dialog>element
In the basic HTML structure, the open button is associated with the <dialog> element.
<!-- Open button -->
<button id="button-open" class="button-icon button-menu"></button>
<dialog id="menu" class="menu-dialog full-menu fullscreen-menu">
<!-- Close button and navigation -->
<button id="button-close" class="button-icon button-close"></button>
<nav class="fullscreen-menu-nav">...</nav>
</dialog>
In JavaScript, use the showModal() method to open the menu and the close() method to close it.
buttonOpen.addEventListener("click", () => {
// Open as a modal
menu.showModal();
});
buttonClose.addEventListener("click", () => {
// Close the menu
menu.close();
});
The hamburger menu is also closed when the backdrop is clicked and when a link inside the menu is clicked. For details, see the following JavaScript code.
CSS for a full-screen menu
To use the <dialog> element as a hamburger menu, several adjustments are needed in the stylesheet. See the shared CSS.
- Override the browser’s default styles for the
<dialog>element - Fixed positioning, spacing, and background color
- The
::backdroppseudo-element - Preventing background scrolling
Preventing background scrolling is handled in the shared CSS.
body:has(dialog[open]) {
/* Prevent the background page from scrolling */
overflow: hidden;
}
Motion
Adding motion to a modal’s open and close states softens the impact of the screen transition and helps guide the user’s attention.
In older implementations, opening and closing motion for modals required JavaScript and was complex and difficult. Recent CSS makes this kind of motion easier to implement. The key points are CSS @starting-style and allow-discrete.
@starting-style is an at-rule that lets you specify the starting state of a CSS transition. The <dialog> element transitions from an initial state (hidden) to visible, then to a closed state (hidden). @starting-style is useful for defining the first initial state (hidden). It is a significant feature worth remembering.
The starting opacity and translate values can be grouped inside @starting-style.
.full-menu[open] {
opacity: 1;
translate: 0 0;
@starting-style {
/* Place it slightly above when opening starts */
opacity: 0;
translate: 0 -24px;
}
}
The idea behind @starting-style is also explained in a video. For details, see the YouTube video starting-styleの解説.
The other feature, transition-behavior: allow-discrete, is a separate concept, but it plays an important role in implementing motion. opacity and translate change continuously as numeric values. For example, values from 0.0 to 1.0 are continuous. By contrast, display is a property without intermediate values, such as block, inline, and none. These are called discrete values.
Switching the discrete display property used to work poorly with CSS transitions, but transition-behavior: allow-discrete lets the switch wait until the motion completes. Write the CSS as follows.
.menu-dialog {
transition:
/* Also wait for discrete value switches */
display 1s allow-discrete,
overlay 1s allow-discrete;
}
For people who are sensitive to motion, the prefers-reduced-motion media query can also be used. See the article CSSでもアクセシビリティに配慮しよう! モーション軽減・文字サイズ変更・ダークモードの実装方法 - ICS MEDIA for details.
Preventing repeated input during transitions
One thing not to forget is preventing repeated input while the modal’s open or close motion is running. Front-end developers have likely experienced a case where a button inside a modal was activated during the opening or closing animation, causing a bug.
The following two styles are specified to suppress clicks while the dialog is closing.
pointer-events: none: blocks only pointer device inputinteractivity: inert: blocks both pointer and keyboard input; the CSS counterpart of HTML’sinertattribute
The sample specifies both in the same place.
.menu-dialog {
/* Stop input during motion */
interactivity: inert;
pointer-events: none;
}
In principle, interactivity: inert alone is enough, but because some browsers do not support it, pointer-events: none is also specified as a fallback. In those browsers, keyboard input cannot be prevented, so this is not a complete safeguard.
Preventing scroll
While the menu is open, scrolling on the main page is stopped. If the page behind the menu moves, the visible position changes after the menu is closed.
If there are many menu items, only the inside of the menu should scroll. Create the scroll area according to the layout: the entire menu in the full-screen version, the panel in the drawer version, and so on.
The scroll-control approach is covered in detail in overscroll-behaviorがお手軽! モーダルUI等のスクロール連鎖を防ぐ待望のCSS.
Drawer menu
A drawer is a menu UI that appears from the edge of the screen. It may be called “drawer navigation,” “slide navigation,” or other names, but it can also be considered a type of hamburger menu. In the drawer version, the outer element is also a <dialog> element.
<dialog class="menu-dialog side-menu" id="menu">
<!-- Panel that appears from the right -->
<div class="side-menu-panel">...</div>
<!-- Close button in the same position as the three-line button -->
<button class="button-icon button-close" id="button-close"></button>
</dialog>
The full-screen and drawer versions run with the same JavaScript.
Menu contents
The contents of a hamburger menu can be implemented freely as part of the site layout. The following variations are examples.
Example: accordion
The accordion is built with the <details> element.
<details>
<!-- Heading area -->
<summary><span class="accordion-title">Residents</span></summary>
<!-- Links shown when expanded -->
<a href="#">Certificates</a>
<a href="#">Health services</a>
</details>
Example: search form
Drawbacks of the dialog element
There are also drawbacks to building this UI with the <dialog> element. A common animation where the three-line menu button continuously morphs into an X close button becomes difficult.
Demo: Micro interactions with CSS animation
When a <dialog> element is opened as a modal, the open button remains outside the dialog. The close button is placed inside the <dialog> element.
Because the open button and the close button are on separate layers, this approach does not pair well with an implementation that continuously transforms a single button according to the visible state.
Column: will JavaScript control soon become unnecessary?
In current browsers, the command / commandfor attributes on the <button> element can perform operations equivalent to the showModal() and close() methods in JavaScript. In addition, if closedby="any" is specified on the <dialog> element, the dialog also closes when the ::backdrop pseudo-element used as the backdrop is clicked. This can greatly reduce the amount of code.
The basics of the command attribute are introduced in the article Using HTML command and interestfor to reduce JavaScript for modals and tooltips.
In the future, the following HTML alone will be able to control the dialog.
<!-- Open dialog with HTML only -->
<button commandfor="menu" command="show-modal"></button>
<dialog id="menu" closedby="any">
<!-- Close dialog with HTML only -->
<button commandfor="menu" command="close"></button>
</dialog>
As of May 2026, the previous-generation iOS 18 Safari does not support attributes such as command. While iOS 18 is still in use, controlling the menu with JavaScript is the safer choice. Around 2027, it seems likely that an era will arrive when hamburger menus can be built without JavaScript.
Browser support
The <dialog> element is available in Chrome 37 (August 2014), Edge 79 (January 2020), Safari 15.4 (March 2022), and Firefox 98 (March 2022) and later. Therefore, it is reasonable to treat the <dialog> element as available in all current browsers.
Reference: <dialog> element | Can I use…
The display motion uses @starting-style, transition-behavior: allow-discrete;, and the interactivity property.
@starting-style is available in Chrome and Edge 117 (September 2023), Safari 17.5 (May 2024), and Firefox 129 (August 2024) and later.
transition-behavior is available in Chrome and Edge 117 (September 2023), Safari 17.4 (March 2024), and Firefox 129 (August 2024) and later.
The interactivity property is available in Chrome and Edge 135 (April 2025) and later. As noted above, pointer-events is used together with it as a fallback.
References:
Column: when to use the popover attribute
For simple menus that do not cover the whole screen, the popover attribute can be used instead of the <dialog> element. The popover attribute is non-modal, so links and forms behind it remain operable. It is suited to cases such as showing a small list from the upper-right of a header or temporarily showing a list of categories.
For a small menu, the popover and popovertarget attributes can be used.
<!-- The button points to the element to show -->
<button popovertarget="menu-compact"></button>
<div id="menu-compact" popover>...</div>
What about UI frameworks?
Menus in UI frameworks are often built with <div> elements rather than the <dialog> element. But there is no need to worry. The large amount of JavaScript on the framework side usually handles accessibility details such as focus trapping.
Reference article:
This article has recommended the <dialog> element, but if those details are already handled by a library, there is no problem using it as is.
Conclusion
This article introduced how to build a hamburger menu with the <dialog> element. If you see a site that still uses a plain <div> element, try operating it with the Tab key and check whether focus is handled properly. If the background can be operated, that is a sign the implementation should be improved.
Using the <dialog> element makes it possible to build a high-quality hamburger menu with less effort, so consider it for your next implementation.
[1]: https://ics.media/en/entry/250904/ "HTML dialog element for modal UIs - ICS MEDIA"

