Components
Menu
Provide command menu interactions with roving focus, typeahead, and stable floating positioning for root and nested menus.
1. Live Preview
vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.
vanilla menu preview activates on the client.
2. Installation
Install adapter-specific dependencies first, then complete setup via the installation guide. installation docs
pnpm add @nake-ui/primitives @nake-ui/adapter-vanilla @nake-ui/theme-vanilla
3. Usage
Minimal runnable usage for the current adapter.
import "@nake-ui/theme-vanilla/styles.css";
import { bindMenu } from "@nake-ui/adapter-vanilla";
const binding = bindMenu(nodes, items, options);
binding.destroy();
4. Examples
Hover
Use `openOnHover` for stable hover opening while sharing the same Floating positioner behavior as React.
import "@nake-ui/theme-vanilla/styles.css";
import { bindMenu } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindMenu(
{
trigger,
positioner,
content,
items: { open: itemOpen, share: itemShare, delete: itemDelete }
},
[
{ id: "open", textValue: "Open" },
{ id: "share", textValue: "Share" },
{ id: "delete", textValue: "Delete" }
],
{ id: "example-hover-menu", defaultOpen: false, openOnHover: true, matchAnchorWidth: false }
);
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Hover me";
Nested
Compose parent and submenu bindings so nested actions open on the right with an arrow cue, aligned with the React demo.
import "@nake-ui/theme-vanilla/styles.css";
import { bindMenu } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const rootBinding = bindMenu(
{
root: rootMenu,
trigger: rootTrigger,
positioner: rootPositioner,
content: rootContent,
items: { rename: itemRename, duplicate: itemDuplicate }
},
[
{ id: "rename", textValue: "Rename" },
{ id: "duplicate", textValue: "Duplicate" }
],
{
id: "example-menu-root",
defaultOpen: false,
matchAnchorWidth: false
}
);
const submenuBinding = bindMenu(
{
root: themeRoot,
trigger: themeTrigger,
positioner: themePositioner,
content: themeContent,
items: { 'theme-light': itemThemeLight, 'theme-dark': itemThemeDark }
},
[
{ id: "theme-light", textValue: "Light" },
{ id: "theme-dark", textValue: "Dark" }
],
{
id: "example-submenu",
defaultOpen: false,
openOnHover: true,
placement: "right-start",
offset: 8,
matchAnchorWidth: false
}
);
// cleanup on unmount
rootBinding.destroy();
submenuBinding.destroy();
}
5. RTL
The same structure running inside a `dir="rtl"` container.
vanilla menu preview activates on the client.
6. Closure Coverage
Complete
Closure Score: 100% (19/19)
Required Checks
-
schemaRegistered -
specDocumented -
reactMapper -
siteExampleCoverage -
siteA11yCoverage -
siteVisible -
schemaValidatorCoverage -
schemaAriaCoverage -
specValidatorRulesSection -
specTestChecklistSection -
specSsrHydrationSection -
reactSsrHydrationCoverage -
reactRenderLevelSsrCoverage -
solidSsrCoverage -
coreImplemented -
primitiveUnitTest -
domContractSnapshot -
vanillaBinding -
adapterParitySnapshot
All required closure checks are currently satisfied.
7. Purpose / Non-goals
Provide command menu interactions with roving focus, typeahead, and stable floating positioning for root and nested menus.
- Rich styled menu presentation
- Submenu visual animation system in core
8. Anatomy & Slot
root(optional structural wrapper in vanilla; always present in React composition)anchor(optional positioning anchor)triggerpositioner(optional floating layer wrapper)contentitem(repeatable)separator(optional)- nested composition: submenu
root+trigger+positioner+content
9. DOM Contract
<div data-ui="menu" data-slot="root" data-state="open">
<button
id="menu-trigger"
data-ui="menu"
data-slot="trigger"
data-state="open"
aria-haspopup="menu"
aria-expanded="true"
aria-controls="menu-content"
></button>
<div
data-ui="menu"
data-slot="positioner"
data-state="open"
data-placement="bottom-start"
data-side="bottom"
>
<div id="menu-content" data-slot="content" data-state="open" role="menu">
<button
data-slot="item"
data-state="highlighted"
data-highlighted="true"
role="menuitem"
tabindex="0"
></button>
<button data-slot="item" data-state="idle" role="menuitem" tabindex="-1"></button>
<div
data-ui="menu"
data-slot="separator"
role="separator"
aria-orientation="horizontal"
></div>
</div>
</div>
</div>
10. State & Events
State Model
- open state: controlled (
open+onOpenChange) and uncontrolled (defaultOpen) - highlighted item id driven by roving focus + typeahead
- optional selection callback (
onSelect/onItemSelect) - selecting an item closes the active menu tree, including all open submenu ancestors
- hover mode state includes close-delay cancellation guards and transition boundary checks
- positioner state reflection via
data-placementanddata-sidewhen floating layer is rendered - first-open measurement settling in vanilla via deferred post-open sync pass
Event Model
- trigger
clicktoggles open state ArrowUp/ArrowDown/Home/Endmove highlight- typeahead search by
textValue Enter/Spaceselect highlighted item and close the current menu tree- item click selects the item and closes the current menu tree
- item
mouseenter/focusupdates highlighted id - outside
pointerdowndismiss for open layers while preserving in-tree menu boundaries - hover mode supports open-on-enter and delayed close on leave with related-target guards
- nested hover transitions keep submenu open while pointer moves across trigger/content corridor
- nested submenu selection closes every open ancestor menu in the same tree
- floating position recalculates on viewport changes; vanilla adds a deferred first-open recalculation for stable initial placement
11. Keyboard & Focus
- roving
tabIndexon enabled items Escapecloses- Tab closes and keeps native focus traversal
- React composition restores trigger focus on Escape close; vanilla keeps native focus behavior unless host code overrides it
12. ARIA
- content:
role="menu" - item:
role="menuitem"andaria-disabledwhen disabled - trigger:
aria-haspopup="menu",aria-expanded,aria-controls - content open/closed reflection via
hidden+data-state
13. Validator Rules
-
structure.invalid-slot-placement -
structure.missing-required-slot -
keyboard.missing-required-keymap -
keyboard.unhandled-typeahead -
keyboard.unhandled-escape -
focus.invalid-tabindex-roving
14. Adapter Notes
Vanilla
- Primary binder: bindMenu.
- Binds explicit DOM nodes and should be cleaned up on unmount.
15. Example Mapping
- No direct example mapping yet.
16. API
Core
-
createMenuControllervalue -
MenuControllertype -
MenuControllerOptionstype -
MenuItemRegistrationtype -
MenuKeyboardResulttype
Vanilla
-
bindMenufunction -
MenuBindingOptionsinterface -
MenuElementsinterface -
MenuPositioningPlacementtype