MorphMenu
A row of menu triggers whose menus open in one floating panel that reshapes from menu to menu instead of closing and reopening.
import { MorphMenu } from "@butteryui/react";Demo
When to use it
Use it for
- Top-level navigation with a few menus of links, each with a caption
- Menus that should feel like one surface as the pointer moves along the row
Not for
- A page's top bar: use NavBar, which holds a MorphMenu
- Actions on a thing: use Menu
Examples
Menu from the page
The open menu kept in state, so the page can open one too, as Browse the guides does.
No menu open
Menus with a story
A site's menus in full: columns of links with captions, a story at the menu's end and a See all link under the columns.
Stacked
With layout="stack", the menus are sections that open in place, one at a time, as in a phone sheet.
Two menus
Two short menus and a plain link.
Variants and states
Menus in one panel
Rest on a trigger for 150ms and its menu unfolds under the row in dur-fast. Move along the row and the panel reshapes on spring.snappy while the columns crossfade, the new ones sliding in from the side you came from. Leave and it folds up after 100ms.
Props
MorphMenu
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Main" | Names the navigation for screen readers. Defaults to "Main". |
value | string | The open menu's value, or "" while none is open. Pair with onValueChange to control it. | |
defaultValue | string | "" | The menu open at first when uncontrolled. Defaults to "", none. |
onValueChange | ((value: string) => void) | Called when a menu opens, another takes over, or the open one closes (""). | |
layout | "bar" | "stack" | "bar" | "bar" (the default) puts the triggers in a row whose menus open in one shared panel. "stack" lists
the menus as sections that open in place, one at a time, as in a nav bar's phone sheet. |
It also takes Omit<ComponentPropsWithRef<"nav">, "defaultValue" | "dir">.
MorphMenu.Column
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | The column's heading, which also names its list of links. |
It also takes Omit<ComponentPropsWithRef<"div">, "title">.
MorphMenu.Entry
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | ReactNode | The link's title, in label. | |
caption | ReactNode | One line under the title, in caption and text-secondary, truncating. |
It also takes Omit<ComponentPropsWithRef<"a">, "title">.
MorphMenu.Item
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | The trigger's text. | |
value | string | Identifies the menu in value. Defaults to a generated id. | |
featured | ReactNode | A column at the menu's end, edge to edge on the section level, such as a story or a promotion. | |
footer | ReactNode | A row under the columns, past a divider, such as a "See all" ArrowLink. | |
children | ReactNode | The menu's columns: MorphMenu.Columns. |
MorphMenu.Link
It also takes ComponentPropsWithRef<"a">.
Accessibility
A navigation landmark ("Main" by default). Triggers are buttons with aria-expanded; the arrow keys move between them, Enter, Space or ArrowDown opens a menu and ArrowDown moves into an open one, and Escape closes it with focus back on its trigger. The pointer never closes or switches a menu that keyboard focus is in. Each column is a list named by its heading. In a stack, each menu is an accordion section.
| Keys | What they do |
|---|---|
| Enter or Space | Opens or closes the focused trigger's menu, or in a stack its section |
| Arrow keys | Move between the triggers and links in the row, without wrapping, or between the sections in a stack |
| Down arrow | Opens the focused trigger's menu, or moves into it once it's open |
| Home or End | Moves to the first or last item in the row |
| Escape | Closes the open menu and puts focus back on its trigger |
| Tab | Goes from an open trigger into its menu, then past the menu's last link to the next trigger, or after the last menu to what follows the row |
Motion
It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.
With reduced motion: The card appears and leaves with a 100ms fade, a switch swaps the columns without sliding while the card takes its new size and place at once, and the chevron turns at once.
Layout
In a row, each trigger is a 36px pill (--bt-h-button) whose press area runs the row's full height, and a row too narrow for its triggers scrolls sideways; a menu opens in one floating card 8px under the row, the menu's size, centered under its trigger and kept inside the row, and scrolls inside when the window has less room below. In a stack each section's trigger is 48px tall and full width, and entry captions truncate to one line. It's its own size container, so it takes the width its parent gives it: in a parent that sizes it to its content, such as a Stack with align="start", it has none, so give it a row of its own.
Related
See it in the Halfmoon shop demo.