A wider screen, please

This page features interactive demos and side-by-side code samples that need at least 340 pixels of horizontal space. Please widen your browser window, or rotate your device, to continue.

Morpheus v0.1.0
v0.1.0

Menu#

Component <neo-menu> is a keyboard-navigable dropdown menu. Author a trigger plus a flat list of <neo-menuitem> / <neo-submenu> / <hr> children; the host wraps them into a fly-out panel and owns hover, focus, ArrowRight / ArrowLeft submenu navigation, Esc, and a global shortcut binding per item: +S works from anywhere on this page.

Deactivate Rename Duplicate
Delete
New state Toggle autoplay
HTML CSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

Replace Patch

Signals

Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.

Patch

<neo-menu>

A triggerless menu panel. Nest it directly inside a <neo-button> (the button becomes the dropdown trigger). The trigger can also be a context menu: nest it in a <neo-contextmenu>  and it opens at the pointer instead. Its children are the rows; it wraps them into a self-contained fly-out and owns hover, focus, ArrowRight-to-enter / ArrowLeft-to-exit, and a global keyboard shortcut per item. From a button host: click or focus the button and press Enter / Space / to open; outside-click, focus loss, or Esc closes.

Reference#

NameTypeDefaultDescription
openboolean reflected observedfalseControls visibility.
placementstring observed"bottom-start"Anchor position relative to the trigger; same values as <neo-popover>.
mode"auto" | "cascade" | "push" observed"auto"Submenu presentation. cascade flies out to the side (desktop). push replaces the parent panel with a back row (mobile). auto picks based on (hover: none) and (pointer: coarse), (max-width: 36rem). Resolved value is mirrored to data-mode-effective.
screen-offsetstring observednoneMinimum margin between the panel and viewport edge; inherited by descendant <neo-submenu>s via the --neo-menu-screen-offset CSS cascade.
clamp-placementboolean observedfalseOpt out of the default strict placement. The menu panel re-anchors its position to keep itself inside the viewport when content would otherwise overflow, instead of staying glued to the configured side and shrinking max-width/max-height. Inherited by descendant <neo-submenu>s unless they set their own value.
min-fit-heightCSS length | "content" observed"content"Minimum vertical slot the strict placement requires. If the slot at the configured side is shorter and the opposite side has more room, the menu auto-flips (e.g. bottom-start -> top-start). content resolves from the menu panel's scroll height on each positioning pass. Inherited by descendant <neo-submenu>s via the --neo-menu-min-fit-height CSS cascade.
min-fit-widthCSS length | "content" observed"content"Same as min-fit-height but for the horizontal axis (left-*/right-*). content resolves from the panel's scroll width. Use 0 to disable the horizontal flip.
NameDescription
hostParent <neo-button> or <neo-contextmenu>: the button becomes the trigger; the context menu opens the panel at the pointer. There is no trigger child.
defaultChildren (<neo-menuitem>, <neo-submenu>, or <hr> separators) are wrapped into the fly-out panel on connect.
NameDetailBubblesDescription
neo-menu-opennoneyesFires when the menu opens.
neo-menu-closenoneyesFires when the menu closes.
neo-menuitem-select{ value: string | null }yesFires when any descendant <neo-menuitem> activates. The root menu auto-closes after.
NameDefaultDescription
--neo-menu-bgvar(--btn-bg)Panel background.
--neo-menu-colorvar(--page-fg)Panel text color.
--neo-menu-border-colorvar(--btn-border)Panel border color.
--neo-menu-border-width1pxPanel border width.
--neo-menu-radiusvar(--page-radius, 0.625rem)Panel corner radius.
--neo-menu-paddingcalc(var(--page-spacing, 0.25rem) * 1)Padding around the panel rows.
--neo-menu-shadow0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 28px rgba(0, 0, 0, 0.18)Panel box shadow.
--neo-menu-min-width12remPanel minimum width.
--neo-menu-z-index1200Panel stacking order.
--neo-menu-item-radiuscalc(var(--page-radius, 0.625rem) * 0.6)Row corner radius.
--neo-menu-item-padding-ycalc(var(--page-spacing, 0.25rem) * 1.5)Vertical padding of a row.
--neo-menu-item-padding-xcalc(var(--page-spacing, 0.25rem) * 2.5)Horizontal padding of a row.
--neo-menu-item-hover-bgcolor-mix(in srgb, var(--page-fg) 8%, transparent)Background of the focused row.
--neo-menu-item-active-bgcolor-mix(in srgb, var(--page-fg) 12%, transparent)Background of an open submenu's trigger row.
--neo-menu-shortcut-colorcolor-mix(in srgb, var(--page-fg) 55%, transparent)Color of the trailing shortcut hint and submenu chevron.
--neo-menu-separator-colorcolor-mix(in srgb, var(--page-fg) 12%, transparent)Color of an <hr> separator.
--neo-menu-separator-width1pxThickness of an <hr> separator.

<neo-menuitem>

A leaf row inside a menu or submenu. Activates on click, Enter, Space, or a global shortcut binding; emits neo-menuitem-select and the enclosing menu collapses the chain.

NameTypeDefaultDescription
valuestringnoneIdentifier carried in the neo-menuitem-select event detail.
disabledboolean observedfalseNon-interactive state. Skips the focus ring, swallows clicks, and ignores the bound shortcut.
shortcutstring observednoneGlobal hotkey, e.g. Mod+S, Cmd+K, Shift+Alt+P. Mod resolves to ⌘ on macOS, Ctrl elsewhere. Renders a trailing kbd-styled hint and registers a document-level keydown that synthesizes activation. Suppressed when the user is typing into an <input>, <textarea>, or contentEditable.
NameDescription
defaultItem label. May contain text, icons, or any other markup.
NameDetailBubblesDescription
neo-menuitem-select{ value: string | null }yesFires on click, Enter, Space, or shortcut activation. The enclosing menu listens and closes the chain.

<neo-submenu>

A nested fly-out. Place it as a child of <neo-menu> or another <neo-submenu>; the host auto-renders the trigger row from label, and the element's children become the panel. Opens on hover, click, or ->; closes and returns focus to the trigger row.

NameTypeDefaultDescription
labelstring observednoneRequired. Trigger row text. The host auto-renders the row + chevron.
openboolean reflected observedfalseControls fly-out visibility.
placementstring observed"right-start"Anchor position relative to the trigger row.
disabledboolean observedfalseDisables the entire branch: the trigger row is unfocusable and hover/click no longer opens.
screen-offsetstring observednoneMinimum margin between the panel and viewport edge; inherits from the ancestor <neo-menu> unless set directly.
clamp-placementboolean observedfalseOpt out of the default strict placement and re-anchor the submenu panel position to fit. The ancestor <neo-menu>'s value is also honored, so usually only the root needs to set it.
min-fit-heightCSS length | "content" observed"content"Minimum vertical slot the strict placement requires; inherits from the ancestor <neo-menu> unless set directly.
min-fit-widthCSS length | "content" observed"content"Same as min-fit-height but for the horizontal axis.
NameDescription
defaultChildren (<neo-menuitem>, nested <neo-submenu>, or <hr> separators) are wrapped into the fly-out panel on connect.
NameDetailBubblesDescription
neo-submenu-opennoneyesFires when the fly-out opens.
neo-submenu-closenoneyesFires when the fly-out closes.

Examples#

Push mode#

Submenus replace the root panel rather than fly out to the side, with a synthesized back row. This is the native mobile pattern and the default for narrow viewports ((hover: none) and (pointer: coarse) or (max-width: 36rem)) when mode is left at its default auto. Forced here to preview on desktop.

File New file Save
Project A.json Project B.json Editor Light Dark

Open above#

Anchor the panel at top-start. Strict placement is the default: the panel stays glued to top-*, shrinks its max-height to the slot above the trigger, and scrolls the items inside. Submenu fly-outs inherit. Toggle clamp-placement on to opt into the alternative: the menu re-anchors below the trigger when there isn't enough room above.

Disabled rows#

Disabled items skip the focus ring, swallow clicks, and ignore their bound shortcut. The submenu's own disabled attribute disables the entire branch.

EditUndo Redo
Cut Copy Paste
Find in file…

Examples: Datastar#

Morphing during interaction#

A Datastar app can fat-morph the page at any moment, so the rows under a <neo-menu> may appear, disappear, or flip to disabled while the menu is open. Autoplay cycles the states below to simulate those server-driven morphs: open the menu and watch it reconcile in place: the trigger and panel stay mounted, the menu stays open, and focus survives.

Deactivate Rename Duplicate
Delete
New state Toggle autoplay
HTML CSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

Replace Patch

Signals

Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.

Patch

Settings

Theme

Default Sunshine Ocean Matrix Rose

Accessibility

Playgrounds

Server Simulator