details-menu turns a details element into an ARIA menu in one tag
A menu opened with <details>.
At a glance
- What is it?
- The details-menu element is a small web component from GitHub that adds menu semantics and keyboard behaviour to content inside a details and summary pair, with hooks for updating a button label and support for lazily loaded menu content. It suits a design system that wants native disclosure markup with proper menu roles, and it is a thin layer rather than a full menu widget.
- Who is it for?
- Adopt details-menu when you want menu semantics and keyboard behaviour on top of native details and summary markup, especially inside a design system that already ships that markup. Skip it if you need a full menu widget with positioning, focus trapping and typeahead beyond what the element provides, and skip it if you must support browsers without custom element support and cannot ship a polyfill.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The element is a details and summary pair with menu semantics added
The component is described in one sentence: a menu that's opened with a details button. The markup is what makes the design clear. A details element holds a summary element that acts as the trigger, and inside it sits a details-menu element with role set to menu, containing buttons with role set to menuitem:
<details>
<summary>Robots</summary>
<details-menu role="menu">
<button type="button" role="menuitem">Hubot</button>
<button type="button" role="menuitem">Bender</button>
<button type="button" role="menuitem">BB-8</button>
</details-menu>
</details>The point of the split between the two elements is that the native details and summary pair already does the disclosure, including the open and closed state and the click-outside behaviour, and the custom element only handles the menu half. So the browser's own disclosure widget is doing the hard part and the component is the part that browsers do not provide. That is a sensible division. It also means the element is a progressive enhancement layer over markup that already means something on its own, rather than a component that generates its own trigger.
Two data attributes turn a menu into a dropdown button
The most useful behaviour is the pair of data attributes that update the trigger when an item is chosen, and it comes in two flavours. With data-menu-button and data-menu-button-text, the text of the element marked with data-menu-button is replaced with the text of the activated item, so a summary can show the current selection:
<details>
<summary>Preferred robot: <span data-menu-button>None</span></summary>
<details-menu role="menu">
<button type="button" role="menuitem" data-menu-button-text>Hubot</button>
<button type="button" role="menuitem" data-menu-button-text>Bender</button>
<button type="button" role="menuitem" data-menu-button-text>BB-8</button>
</details-menu>
</details>The second variant, data-menu-button-contents, updates the trigger's HTML content rather than its text, which is what you need when the selection is shown as an image or a fragment of markup rather than a string. The distinction between setting text and setting inner HTML is exactly the kind of thing that gets decided for you here, and the safe option is the one you pick. There is no scripting required to use either.
Radio and checkbox items work through a label, and the input state stays in sync
For selection menus, the component supports label elements with tabindex of 0 and a role of menuitemradio or menuitemcheckbox, and the documentation states that the check states of the input element and the label will be synchronized. The markup puts a real input inside the label:
<details>
<summary>Preferred robot</summary>
<details-menu role="menu">
<label tabindex="0" role="menuitemradio">
<input type="radio" name="robot" value="Hubot"> Hubot
</label>
<label tabindex="0" role="menuitemradio">
<input type="radio" name="robot" value="Bender"> Bender
</label>
<label tabindex="0" role="menuitemradio">
<input type="radio" name="robot" value="BB-8"> BB-8
</label>
</details-menu>
</details>This is the design decision that makes the component more than a menu skin. Because the state lives in a native form control, form submission, reset and value reading all work without a hidden model, and the tabindex on the label is what lets the item receive focus so that the arrow keys can move between options. A menu built on divs and a JavaScript model would need all of that reimplemented. The cost is that the markup is more verbose, and the label has to carry a role and a tabindex for the behaviour to attach.
Two custom events report the selection, in different phases
The element fires two events, and the difference between them is a timing contract you can rely on. The details-menu-select event is cancelable and is fired on the details-menu element with event.detail.relatedTarget being the item to be selected, which means it fires before the choice is committed and can be prevented. The details-menu-selected event is fired on the same element with event.detail.relatedTarget being the item that was selected, and it happens after the label is updated and the menu is closed. So a handler that wants to intercept the choice uses the first, and a handler that wants to react to the committed state uses the second. The relatedTarget naming is slightly confusing since it holds the item rather than the previously focused element, but it is the documented contract and it is the only way to know which item was chosen.
Deferred loading reuses the include-fragment element
Menu content can be loaded from a server, and the mechanism reuses another GitHub web component rather than inventing a fetching convention. The details-menu element takes a src attribute and an optional preload attribute, and the content is an include-fragment element:
<details>
<summary>Robots</summary>
<details-menu src="/robots" preload>
<include-fragment>Loading…</include-fragment>
</details-menu>
</details>The behaviour is worth understanding in detail because the two attributes do different things. The src attribute value is copied to the include-fragment the first time the details button is toggled open, and that is what starts the server fetch. If the preload attribute is present, the server fetch begins on mouse hover over the details button, so by the time the menu is actually opened the content may already be there. That is a genuine improvement on a request that starts only when the user opens the menu, and it works for a menu with a hundred items without shipping them all. The include-fragment element is a separate project with its own documentation, and this component depends on it rather than embedding a copy of the loading logic.
Installation is one npm command and the import is one line
The package is published as @github/details-menu-element and the install is:
$ npm install @github/details-menu-elementYou then either import it as a module, which registers the element:
import '@github/details-menu-element'or load it with a script tag pointing at the built file in node_modules:
<script type="module" src="./node_modules/@github/details-menu-element/dist/index.js">The script tag example in the README is missing its closing angle bracket, which is a small documentation slip rather than a design problem, but it is worth knowing if you copy it. The examples directory holds a single index.html, and the published site at github.github.io/details-menu-element/examples is the live version of that demonstration, so it is the fastest way to see the behaviour before installing anything. The browser support note is short: browsers without native custom element support require a polyfill, and the list of supported browsers given is Chrome, Firefox, Safari and Microsoft Edge.
The build is TypeScript compiled to a single dist file, tested in a real browser
The package metadata shows a small and modern toolchain. The package version in the file is 1.0.12 while the most recent published tag is v1.0.13 from 2022-10-24, so the tag and the manifest are slightly out of step, which is normal for a project that tags releases and then updates the manifest. The build is tsc, with a prebuild step that cleans dist, runs the linter and creates the directory, and the lint script is eslint across JavaScript and TypeScript files followed by tsc --noEmit, so type errors fail the build. The tests run under karma with a Chrome launcher, using mocha and chai, and the pretest script builds first, so the test run always exercises the compiled output rather than the sources. The published files are just dist, and the package is a module with types at dist/index.d.ts. The repository also has a devcontainer, a CODEOWNERS file and a tsconfig, which is the shape of a component that lives inside a larger product organisation.
Where details-menu is the wrong choice
Three situations argue against it. The first is a menu that needs real widget behaviour. What this component provides is menu semantics and keyboard handling on content inside a details element, and the disclosure itself comes from the native element. Anything that needs a floating panel positioned against a button, with collision handling and outside click dismissal, or a roving focus implementation, or typeahead over a long list, is a different kind of component and this one does not claim to be it. The second is a project that cannot ship a polyfill. The browser support note is explicit that a polyfill is required outside the four listed browsers, and a custom elements polyfill is a real dependency with its own lifecycle quirks, so a product that has decided not to ship one cannot use this. The third is a design system that has already standardised on a menu primitive. Duplicating a menu implementation inside a system that has one is a maintenance liability, and the argument for this element is specifically that it fits native details markup. There is also a quiet signal in the release history. The last tag is from October 2022, the previous ones from August 2021, while the last push to the repository was on 2026-10-20, so the code is being touched but the published versions are old. For a component that is versioned independently, that gap is what you would want to see close before adopting it in a long-lived product.
Editorial conclusion
Adopt details-menu when you want menu semantics and keyboard behaviour on top of native details and summary markup, especially inside a design system that already ships that markup. Skip it if you need a full menu widget with positioning, focus trapping and typeahead beyond what the element provides, and skip it if you must support browsers without custom element support and cannot ship a polyfill. Verify first whether your browsers include Chrome, Firefox, Safari and Microsoft Edge, since the documentation names those four and requires a polyfill elsewhere.
Frequently asked questions
What is the details-menu element?
It is a web component from GitHub that provides a menu opened with a details button. The details-menu element is placed inside a details and summary pair and carries role set to menu, with menu items as buttons or labels, and it manages the menu keyboard behaviour and selection events.
How do I install details-menu-element?
Run npm install @github/details-menu-element, then import it as a module with import '@github/details-menu-element' or load it with a script tag pointing at ./node_modules/@github/details-menu-element/dist/index.js.
How do I update the button text when a menu item is selected?
Mark the element that shows the current value with data-menu-button and mark the items with data-menu-button-text, which replaces the text on activation. Use data-menu-button-contents instead when the trigger should show HTML content such as an image rather than a plain string.
What events does details-menu fire?
details-menu-select is cancelable and fires on the details-menu element with event.detail.relatedTarget being the item to be selected. details-menu-selected fires after the label is updated and the menu is closed, with the same property holding the item that was selected.
Is details-menu-element still maintained?
The repository is not archived and the last push was on 2026-10-20, but the most recent published tag is v1.0.13 from 2022-10-24 and the package version in package.json is 1.0.12. The licence is MIT, browsers without native custom element support require a polyfill, and the supported list is Chrome, Firefox, Safari and Microsoft Edge.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/github-details-menu-element)Was this useful?