# Menu
A pure HTML and vanilla JS implementation of Zag JS Menu
An accessible dropdown and context menu that is used to display a list of actions or options that a user can choose.
# Anatomy
The Menu component consists of the following data parts:
trigger
, context-trigger
, indicator
, positioner
, content
, separator
, item
, item-group
, item-group-label
<div class="menu menu-js">
<button data-part="trigger">
File
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
</svg>
</span>
</button>
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="new">
New Document
</li>
<li data-part="item" data-value="open">
Open
</li>
<li data-part="item" data-value="save">
Save
</li>
<div data-part="separator"></div>
<li data-part="item" data-value="export">
Export
</li>
<li data-part="item" data-value="exit">
Exit
</li>
</ul>
</div>
</div>
# Group items
The Menu items can be grouped with a label to identify each group
Please note you must provide and data-id
to item-group
and data-htmlFor
to item-group-label
<div class="menu menu-js">
<button data-part="trigger">
View
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
</svg>
</span>
</button>
<div data-part="positioner">
<div data-part="content">
<div data-part="item-group" data-id="layout">
<div data-part="item-group-label" data-htmlFor="layout">Layout</div>
<li data-part="item" data-value="grid-view" data-id="layout">
Grid View
</li>
<li data-part="item" data-value="list-view">
List View
</li>
</div>
<div data-part="item-group" data-id="zoom">
<div data-part="item-group-label" data-htmlFor="zoom">Zoom</div>
<li data-part="item" data-value="zoom-in" data-id="zoom">
Zoom In
</li>
<li data-part="item" data-value="zoom-out" data-id="zoom">
Zoom Out
</li>
</div>
</div>
</div>
</div>
# Nested Menu
The Menus can be nested with data-children
on the parent menu and data-child
on the trigger-item
and indicator
<div class="menu menu-js" data-children="preferences" data-aria-label="Main Menu">
<button data-part="trigger">
Tools
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5">
</path>
</svg>
</span>
</button>
<div data-part="positioner">
<ul data-part="content">
<li data-part="trigger-item" data-child="preferences">Preferences
<span data-part="indicator" data-child="preferences">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</span>
</li>
</ul>
</div>
</div>
<div id="preferences" class="menu menu-js" data-aria-label="Preferences Menu" data-offset-main-axis="5"
data-placement="right-start">
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="general">
General
</li>
<li data-part="item" data-value="appearance">
Appearance
</li>
</ul>
</div>
</div>
# Subnested Menu
You can nest as many menus, please note that you must provide unique ids for each menu
<div class="menu menu-js" data-children="developer-tools" data-aria-label="Main Menu">
<button data-part="trigger">
Developer
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5">
</path>
</svg>
</span>
</button>
<div data-part="positioner">
<ul data-part="content">
<li data-part="trigger-item" data-child="developer-tools">Developer Tools
<span data-part="indicator" data-child="developer-tools">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</span>
</li>
</ul>
</div>
</div>
<div id="developer-tools" class="menu menu-js" data-children="debugging-options" data-aria-label="Developer Tools Menu"
data-offset-main-axis="5" data-placement="right-start">
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="console">
Console
</li>
<li data-part="item" data-value="network">
Network Tab
</li>
<li data-part="trigger-item" data-child="debugging-options">Debugging
<span data-part="indicator" data-child="debugging-options">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</span>
</li>
</ul>
</div>
</div>
<div id="debugging-options" class="menu menu-js" data-aria-label="Debugging Options Menu" data-offset-main-axis="5"
data-placement="right-start">
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="breakpoints">
Set Breakpoint
</li>
<li data-part="item" data-value="step-over">
Step Over
</li>
</ul>
</div>
</div>
# Context Menu
An accessible dropdown and context menu that is used to display a list of actions or options that a user can choose when a trigger element is right-clicked or long pressed.
<div class="menu menu-js">
<div data-part="context-trigger">
Right Click/ Long Press
</div>
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="copy">
Copy
</li>
<li data-part="item" data-value="paste">
Paste
</li>
<li data-part="item" data-value="duplicate">
Duplicate
</li>
<div data-part="separator"></div>
<li data-part="item" data-value="inspect">
Inspect Element
</li>
<li data-part="item" data-value="bookmark">
Bookmark Page
</li>
</ul>
</div>
</div>
With groups
<div class="menu menu-js">
<div data-part="context-trigger">
<div>Right Click/ Long Press</div>
</div>
<div data-part="positioner">
<div data-part="content">
<div data-part="item-group-label" data-htmlFor="format">Format</div>
<div data-part="item-group" data-id="format">
<div data-part="item-group-label" data-htmlFor="format">Format</div>
<li data-part="item" data-value="bold">
Bold
</li>
<li data-part="item" data-value="italic">
Italic
</li>
</div>
<div data-part="separator"></div>
<div data-part="item-group" data-id="align">
<div data-part="item-group-label" data-htmlFor="align">Align</div>
<li data-part="item" data-value="align-left">
Align Left
</li>
<li data-part="item" data-value="align-center">
Align Center
</li>
</div>
</div>
</div>
</div>
# Data attributes
Each menu can be set with different settings with the following data-attribute.
<div class="menu menu-js" id="custom-menu"
data-aria-label="Demo menu" data-close-on-select="false" data-composite="true" data-default-highlighted-value="profile" data-highlighted-value="profile" data-default-open="true" data-dir="ltr" data-loop-focus="false" data-typehead="true" data-placement="top-end" data-strategy="absolute" data-flip="true" data-hide-when-detached="true" data-gutter="1" data-arrow-padding="1" data-overflow-padding="1" data-slide="true" data-fit-viewport="true" data-overlap="true" data-same-width="true">
<button data-part="trigger">
Custom Menu
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
</svg>
</span>
</button>
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="new">
New
</li>
<li data-part="item" data-value="open">
Open
</li>
<li data-part="item" data-value="save">
Save
</li>
<div data-part="separator"></div>
<li data-part="item" data-value="export">
Export
</li>
<li data-part="item" data-value="exit">
Exit
</li>
</ul>
</div>
</div>
id
Type: string
Description: Unique id of the component. Default generated if none is provided.
data-aria-label
Type: string
Description: The accessibility label for the menu.
data-close-on-select
Type: boolean
Default: true
Description: Whether to close the menu when an option is selected.
data-composite
Type: boolean
Default: true
Description: Whether the menu is composed with other composite widgets like a combobox or tabs.
data-default-highlighted-value
Type: string | null
Description: The initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value.
data-highlighted-value
Type: string | null
Description: The controlled highlighted value of the menu item.
data-default-open
Type: boolean
Description: The initial open state of the menu when rendered. Use when you don't need to control the open state.
data-dir
Type: "ltr" | "rtl"
Default: "ltr"
Description: The text direction of the menu.
data-loop-focus
Type: boolean
Default: false
Description: Whether to loop the keyboard navigation.
data-typehead
Type: boolean
Default: true
Description: Whether pressing printable characters should trigger typeahead navigation.
data-placement
Type: Placement
Default: "bottom-start"
Description: The placement of the menu relative to its trigger.
data-strategy
Type: "absolute" | "fixed"
Default: "absolute"
Description: The CSS positioning strategy for the menu.
data-flip
Type: boolean
Default: true
Description: Whether to flip the placement of the menu when it overflows.
data-hide-when-detached
Type: boolean
Default: false
Description: Whether to hide the menu when its trigger is no longer in the DOM or detached from layout.
data-gutter
Type: number
Default: 8
Description: The offset (in pixels) between the menu and its trigger.
data-arrow-padding
Type: number
Default: 0
Description: The minimum padding (in pixels) between the arrow and the edges of the menu.
data-overflow-padding
Type: number
Default: 8
Description: The minimum padding (in pixels) between the menu and the viewport.
data-slide
Type: boolean
Default: false
Description: Whether to animate the menu with a slide effect.
data-fit-viewport
Type: boolean
Default: false
Description: Whether to resize the menu to fit within the viewport.
data-overlap
Type: boolean
Default: false
Description: Whether the menu can overlap its trigger.
data-same-width
Type: boolean
Default: false
Description: Whether the menu should match the width of its trigger.
# Event Callbacks
Each Menu component can receive callbacks that can be used to respond to user interaction with custom behavior.
You must add a custom id for the menu and a event listener for your event name
document.getElementById("my-menu")
?.addEventListener("my-menu-event", (event) => {
console.log("Received event:", (event as CustomEvent).detail);
});
<div id="my-menu" class="menu menu-js" data-on-select="my-menu-event">
<button data-part="trigger">
File
<span data-part="indicator">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
</svg>
</span>
</button>
<div data-part="positioner">
<ul data-part="content">
<li data-part="item" data-value="new">
New Document
</li>
<li data-part="item" data-value="open">
Open
</li>
<li data-part="item" data-value="save">
Save
</li>
<div data-part="separator"></div>
<li data-part="item" data-value="export">
Export
</li>
<li data-part="item" data-value="exit">
Exit
</li>
</ul>
</div>
</div>
Open your browser's console to see the events received when an item is selected
data-on-select
Type: string
Description: Event name to be sent when a menu item is selected. The event detail contains the selected item's value.
data-on-open-change
Type: string
Description: Event name to be sent when the menu opens or closes. The event detail contains the menu's open state.
data-on-escape-key-down
Type: string
Description: Event name to be sent when the Escape key is pressed while the menu is focused.
data-on-focus-outside
Type: string
Description: Event name to be sent when focus moves outside the menu.
data-on-highlight-change
Type: string
Description: Event name to be sent when the highlighted menu item changes. The event detail contains the current highlighted value.
data-on-interact-outside
Type: string
Description: Event name to be sent when an interaction happens outside the menu.
data-on-pointer-down-outside
Type: string
Description: Event name to be sent when a pointer down event occurs outside the menu.
data-on-navigate
Type: string
Description: Event name to be sent to navigate to the selected item if it's an ancho
# Installation
First, complete the Corex UI initial installation guide for your platform, bundler, or framework.
# Static
- Import the Menu component
import "@corex-ui/static/components/menu"
This will automatically initialize all elements with class="menu-js"
and add the necessary interaction behavior.
- Add styling
To apply the default Corex UI design system styles, import the stylesheet:
@import "@corex-ui/design/components/menu.css";
Then apply the base class along with any desired modifiers:
<div class="menu menu-js">
<button data-part="trigger">File</button>
<div data-part="positioner">
<ul data-part="content"></ul>
</div>
</div>
# Static React
Experimental! Only works with React static export (eg. Next.js SSG.)
- Import the Menu component
import { Menu } from '@corex-ui/static/react';
export default function Home() {
return (
<Menu>
<button data-part="trigger">File</button>
<div data-part="positioner">
<ul data-part="content" />
</div>
</Menu>
);
}
- Add styling
To apply the default Corex UI design system styles, import the stylesheet:
@import "@corex-ui/design/components/menu.css";
Then apply the base class along with any desired modifiers:
<Menu className="menu">
{/* content */}
</Menu>