j3w1 / MenuVue · HTML · React · Astro

J3W1 / DESIGN SYSTEM

Menu

A menu button that opens a list of actions on the raised surface inside a 1px overlay border; the active item is the selection fill and check items show a glyph.

stable · LIGHT DOM · 1.1.0

Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.

Live variants

default

<j3w1-menu id="example-menu-default"><div class="menu">
  <button class="menu-button" type="button" id="example-menu-default-menu-file-button" aria-haspopup="menu" aria-expanded="true" aria-controls="example-menu-default-menu-file">File<span class="menu-chevron" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" focusable="false"><path d="M4 6l4 4 4-4"></path></svg></span></button>
  <ul class="menu-list" role="menu" id="example-menu-default-menu-file" aria-labelledby="example-menu-default-menu-file-button">
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="0"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">New window</span><span class="menu-shortcut">Mod+Enter</span></button></li>
    <li role="none"><button class="menu-item menu-item-demo-target" role="menuitem" type="button" tabindex="-1"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Open…</span><span class="menu-shortcut">Mod+O</span></button></li>
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="-1" aria-disabled="true"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Save</span><span class="menu-shortcut">Mod+S</span></button></li>
    <li class="menu-separator" role="separator"></li>
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="-1"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Quit</span><span class="menu-shortcut">Mod+Shift+Q</span></button></li>
  </ul>
</div></j3w1-menu>

with-submenu

<j3w1-menu id="example-menu-with-submenu"><div class="menu">
  <button class="menu-button" type="button" id="example-menu-with-submenu-menu-view-button" aria-haspopup="menu" aria-expanded="true" aria-controls="example-menu-with-submenu-menu-view">View<span class="menu-chevron" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" focusable="false"><path d="M4 6l4 4 4-4"></path></svg></span></button>
  <ul class="menu-list" role="menu" id="example-menu-with-submenu-menu-view" aria-labelledby="example-menu-with-submenu-menu-view-button">
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="0"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Reload</span><span class="menu-shortcut">Mod+R</span></button></li>
    <li role="none"><button class="menu-item menu-item-demo-target" role="menuitem" type="button" tabindex="-1" aria-haspopup="menu" aria-expanded="false" aria-controls="example-menu-with-submenu-submenu-0" id="example-menu-with-submenu-submenu-0-trigger"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Density</span><span class="menu-submenu" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" focusable="false"><path d="M6 4l4 4-4 4"></path></svg></span></button><ul class="menu-list" role="menu" id="example-menu-with-submenu-submenu-0" aria-labelledby="example-menu-with-submenu-submenu-0-trigger" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitemradio" aria-checked="true" tabindex="-1" data-action="submenu-0:first">First option</button></li><li role="none"><button class="menu-item" type="button" role="menuitemradio" aria-checked="false" tabindex="-1" data-action="submenu-0:second">Second option</button></li></ul></li>
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="-1" aria-haspopup="menu" aria-expanded="false" aria-controls="example-menu-with-submenu-submenu-1" id="example-menu-with-submenu-submenu-1-trigger"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Profile</span><span class="menu-submenu" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" focusable="false"><path d="M6 4l4 4-4 4"></path></svg></span></button><ul class="menu-list" role="menu" id="example-menu-with-submenu-submenu-1" aria-labelledby="example-menu-with-submenu-submenu-1-trigger" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitemradio" aria-checked="true" tabindex="-1" data-action="submenu-1:first">First option</button></li><li role="none"><button class="menu-item" type="button" role="menuitemradio" aria-checked="false" tabindex="-1" data-action="submenu-1:second">Second option</button></li></ul></li>
    <li class="menu-separator" role="separator"></li>
    <li role="none"><button class="menu-item" role="menuitem" type="button" tabindex="-1"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Full screen</span><span class="menu-shortcut">F11</span></button></li>
  </ul>
</div></j3w1-menu>

with-checks

<j3w1-menu id="example-menu-with-checks"><div class="menu">
  <button class="menu-button" type="button" id="example-menu-with-checks-menu-options-button" aria-haspopup="menu" aria-expanded="true" aria-controls="example-menu-with-checks-menu-options">Options<span class="menu-chevron" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" focusable="false"><path d="M4 6l4 4 4-4"></path></svg></span></button>
  <ul class="menu-list" role="menu" id="example-menu-with-checks-menu-options" aria-labelledby="example-menu-with-checks-menu-options-button">
    <li role="none"><button class="menu-item" role="menuitemcheckbox" type="button" tabindex="0" aria-checked="true"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Line numbers</span></button></li>
    <li role="none"><button class="menu-item menu-item-demo-target" role="menuitemcheckbox" type="button" tabindex="-1" aria-checked="false"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Wrap lines</span></button></li>
    <li role="none"><button class="menu-item" role="menuitemcheckbox" type="button" tabindex="-1" aria-checked="true"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Render whitespace</span></button></li>
    <li class="menu-separator" role="separator"></li>
    <li role="none"><button class="menu-item" role="menuitemcheckbox" type="button" tabindex="-1" aria-checked="false" aria-disabled="true"><span class="menu-check" aria-hidden="true">✓</span><span class="menu-label">Minimap</span></button></li>
  </ul>
</div></j3w1-menu>

Install and register

Use the exact version. If npm publication is pending, install the release tarball from the installation guide.

npm install --save-exact @j3w1/ui@1.1.0
import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/menu.css';
import '@j3w1/ui/register/menu';

Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.

Vue SFC, React, Astro and plain HTML integration →

Copy the complete implementation

npx --package=@j3w1/ui@1.1.0 j3w1-ui copy menu --out ./vendor/j3w1/menu

The CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.

Public API

attributes

NameType / signatureMeaning
disabledCustomEventDisable the native controls owned by this instance.
loadingCustomEventMark the primary control busy and prevent duplicate activation.

properties

NameType / signatureMeaning
disabledbooleanDelegates disabled state to native children.
namestringNative control name; application form keys remain explicit.
openbooleanMenu visibility.

methods

NameType / signatureMeaning
refresh(): voidReconnect behavior after replacing native child structure.
focus(options?: FocusOptions): voidFocus the first usable native control.
show(): voidOpen and focus the first menu item.
hide(focus?: boolean): voidClose all menus, optionally restoring the trigger.

events

NameType / signatureMeaning
j3w1-actionCustomEventAn enabled action or check item was invoked.

Dependencies and verification

Registered components: none. Style dependencies: none.

Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.

Full implementation manifest, API and token dependency closure

Canonical specification

Purpose

Offers a short list of actions behind one button. A menu is for commands; choosing a value belongs in select or combobox, and moving between views in tabs or the sidebar. Menus close as soon as an action runs.

Anatomy

A menu button, styled as a secondary button, that opens a list on color.surface.raised inside a 1px color.border.overlay frame. Items are buttons in color.text.default with a check gutter at the inline start, the label, and an optional shortcut hint in color.text.muted. Separators are 1px color.border.divider rules. The active item, whether reached by pointer or by arrow key, is the selection fill color.interaction.selection.bg with color.interaction.selection.text. The reference renders the menu open in flow beneath the button; a host positions it at z.popover.

States

StateVisualNon-colour channel
defaultthe button at rest; the list on color.surface.raised with the color.border.overlay frame
hoveritem background → color.interaction.hover.bg-strong; text → color.text.link-hovercursor: pointer
focus-visiblering 1px dashed color.interaction.focus.ring at −2px on the button; on the active item the ring is color.interaction.focus.ring-containerthe ring
openbutton fill color.interaction.pressed.bg with 1px color.border.active; the list shownaria-expanded="true"; the list is present
closedthe button at rest; the list hiddenaria-expanded="false"; the list is absent from the tree
disableditem text color.text.disabled; no hover; still focusable by arrow keys when aria-disabledaria-disabled="true"; cursor: not-allowed
checkedthe ✓ glyph in color.text.bright in the gutteraria-checked="true"; the glyph
selectedthe active item: fill color.interaction.selection.bg with color.interaction.selection.textfocus is on the item; aria-activedescendant where used

Precedence: disabled > selected > hover; focus-visible is always drawn.

Keyboard

The button opens on Enter, Space or Down (focus on the first item) and on Up (focus on the last). Inside, Up and Down move and wrap, Home and End jump, Right opens a submenu and Left closes it, typing a letter jumps to the next matching item. Enter or Space activates; a check item toggles without closing. Escape closes and returns focus to the button; Tab closes and continues the tab order. Focus never rests on a separator.

Accessibility

The button carries aria-haspopup="menu", aria-expanded and aria-controls; the list role="menu" and aria-labelledby pointing at the button. Items are role="menuitem" or role="menuitemcheckbox" with aria-checked; disabled items use aria-disabled so they remain reachable and announced. Shortcut hints are text, not aria-keyshortcuts alone. Contrast: item text 8.43:1 and shortcut 5.66:1 on the raised surface, active text 12.47:1 on the fill, the frame 4.57:1, ring-container 7.48:1 on the fill. Items are the row height, at least 24 CSS pixels in compact density.

Portability

A framed box with rows; the active row is a fill, the frame a border, the ring an outline. Toolkits with native menus keep their keyboard model and take the surface, frame and fill colours. Hosts that cannot draw the frame in the overlay colour draw it in the strong border colour and record the deviation.

Non-examples

A shadow under the menu. Rounded corners on the menu or the items. An active item marked by text colour alone. A hover fill that differs from every other menu in the product. Icons in the check gutter of a plain menuitem. A menu that stays open after an action. Items that are links styled as menu items without role="menuitem". Disabling by opacity.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification