JGuang

下拉菜单

DropdownMenu · Astryx

交互预览
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

下拉菜单 · DropdownMenu

下拉菜单用于操作场景。下面的官方示例展示原生用法与状态,属性名保持上游 API。

安装与前置条件

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

需要 React 19+,按顺序导入基础 CSS 和主题,并设置 Theme Provider。参见开始使用。

示例与使用方式

  • 下拉菜单 · 综合展示 / 分组 1:首先体验这个示例,再查看代码理解组件的组合方式。
  • 下拉菜单 · 综合展示 2:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 综合展示 3:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 交互示例 4:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 交互示例 5:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 分组 6:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 基础用法 7:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 交互示例 8:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 禁用状态 9:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 交互示例 10:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 下拉菜单 · 交互示例 11:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。

API 与上游详细参考

优先使用 0.7.1 官方中文说明;上游尚未提供中文的条目保留英文。属性名、类型及默认值保持原生 API,不把示例状态名称当成组件属性。

A dropdown menu that displays a list of actionable items in a popup triggered by a button. Use to present action options as a next step in a process, or to offer contextual actions without cluttering the interface.

属性类型默认值说明
buttonDropdownMenuButtonProps{ label: 'Menu' }Props for the trigger button (Button props except onClick). Mutually exclusive with trigger.
renderTrigger(props: DropdownMenuTriggerProps) => ReactNodeRender the control the menu hangs off — an IconButton, a chip, an avatar, a list row — instead of the built-in Button. Spread the given props onto it: they carry the press model (a mouse opens on press-down, a held finger opens with the finger down), the keyboard opens, the toggle click, and aria-haspopup/aria-expanded/aria-controls/id; the menu is then named by that control through aria-labelledby. Mutually exclusive with button.
items *DropdownMenuOption[]Array of menu entries. Each entry is one of: an action item {label, onClick?, href?, target?, rel?, icon?, description?, endContent?, isDisabled?, variant?, hasCloseOnSelect?, id?} (href makes the row a real link, so a modified click keeps the browser's meaning; variant "destructive" renders it in the error color; endContent holds trailing content such as a keyboard-shortcut hint; id is the row's stable React key, needed only when the array reorders or filters), a divider {type: "divider"}, or a section {type: "section", title?, id?, items: [...action items]}.
presentation'popover' | 'bottom-sheet' | 'adaptive''popover'Presentation surface for data-driven items. 'popover' stays anchored, 'bottom-sheet' always renders the actions in a modal BottomSheet, and 'adaptive' uses a BottomSheet on compact coarse-pointer layouts while remaining anchored elsewhere. Compound children currently support popover only.
isMenuOpenbooleanControlled open state for the menu. Mounting with true renders the menu open without moving focus into it; focus moves to the first item only when the menu opens after mount.
onOpenChange(isOpen: boolean) => voidCallback fired when the open state changes.
menuWidthnumber | stringMinimum width for the popover presentation. Length values may grow for content; intrinsic and CSS-wide keywords select the preferred inline size. Every form is capped to the available viewport space. Defaults to matching the trigger width up to that cap.
menuMaxHeightnumberMaximum height in pixels for the popover presentation, for a menu that must fit its rows. Lifts the default 300px cap; the viewport still bounds it.
headerReactNodeContent above the rows and outside the menu itself — a filter field, a toggle. A menu holds only its rows, so a control that is not a row goes here or in footer, never among the rows. It keeps its own keys and presses. With a header or footer the popover is a modal dialog around the menu: Tab and Shift+Tab move through the header, the menu and the footer and wrap at either end, and Escape closes. The bottom sheet shows it above the actions.
footerReactNodeContent below the rows and outside the menu itself — a "Create" button. Tab from the menu moves into it, and Tab past its last control wraps to the start of the dialog (see header). The bottom sheet shows it below the actions.
placement'above' | 'below' | 'start' | 'end''below'Popover placement relative to the trigger. Ignored by the bottom-sheet presentation. Logical: start/end resolve against the menu's own inherited direction (RTL mirrors).
alignment'start' | 'center' | 'end''start'Popover alignment along the placement axis. Ignored by the bottom-sheet presentation. Logical: start/end follow the menu's own inherited direction (RTL mirrors).
onClick() => voidCallback fired for accepted trigger activation. The trailing click from the same press that light-dismissed the menu is ignored.
hasChevronbooleantrueWhether to show a chevron icon on the trigger button. Set to false for icon-only triggers.
childrenReactNodeCompound-mode menu content: DropdownMenuItem, DropdownMenuDivider, DropdownMenuSubMenu (a flyout on a laptop, a drilled-in view with a Back row on a phone), and the selectable items. Mutually exclusive with items.

使用建议

  • Keep menu items concise and action-oriented so users can scan options quickly.
  • Use sections and dividers to group related actions when the menu has many items.
  • For a short, flat action set, use presentation="bottom-sheet" when product policy calls for a modal touch surface.
  • Use presentation="adaptive" when the same short action set should remain anchored on pointer layouts and become a BottomSheet on compact coarse-pointer layouts.
  • For a hierarchy that cannot fit as adjacent flyouts on a compact touch surface, a product may explicitly use a drill-in interaction with a Back action.
  • Choose presentation explicitly in product code. A compact, coarse-pointer, hover-free media query is one useful policy, but DropdownMenu does not impose a universal device breakpoint.
  • When the content is no longer a short list of immediate actions, reevaluate the interaction and choose a component that matches the actual task; content traits alone do not determine the component.
  • 避免:Use a DropdownMenu for navigation; use a navigation component instead.
  • 避免:Place more than 10–12 items in a single menu without grouping them into sections.

主题 API

{
  "targets": [
    {
      "className": "astryx-dropdown-menu",
      "visualProps": [
        "presentation"
      ]
    },
    {
      "className": "astryx-dropdown-menu-item",
      "visualProps": [
        "size",
        "variant"
      ]
    },
    {
      "className": "astryx-dropdown-menu-radio",
      "visualProps": [
        "size"
      ],
      "states": [
        "checked",
        "disabled"
      ]
    },
    {
      "className": "astryx-dropdown-menu-section-heading"
    },
    {
      "className": "astryx-dropdown-menu-divider"
    },
    {
      "className": "astryx-dropdown-menu-indicator-icon"
    }
  ],
  "vars": [
    {
      "name": "--_dropdown-menu-radius",
      "description": "Border radius of the menu popup",
      "default": "var(--radius-element)",
      "private": true
    },
    {
      "name": "--_dropdown-menu-padding",
      "description": "Inner padding of the menu popup",
      "default": "var(--spacing-1)",
      "private": true
    }
  ],
  "derived": [
    {
      "property": "borderRadius",
      "vars": [
        "--_dropdown-menu-radius"
      ]
    },
    {
      "property": "padding",
      "vars": [
        "--_dropdown-menu-padding"
      ]
    }
  ]
}

组件结构

  • Trigger button: Button that opens and closes the selected menu presentation.
  • Trigger indicator icon: Optional trailing chevron shown by a labeled trigger when hasChevron is enabled.
  • Pointer menu surface: Anchored top-level or nested menu panel used by the pointer presentation.
  • Pointer action row: Action, selectable option, or submenu trigger row in an anchored menu.
  • Icon-rendered item icon: Optional semantic or component icon rendered through Icon at the start of an action row.
  • Caller-rendered item start content: Optional arbitrary React content rendered directly at the start of an action row.
  • Checkbox indicator: Decorative shared checkbox indicator for a checkbox action row.
  • Radio indicator: Decorative shared radio indicator with an additional menu-owned target.
  • Pointer section heading: Heading that labels a group of rows in an anchored menu: a data-driven section or a compound DropdownMenuGroup.
  • Pointer divider: Divider between groups in an anchored menu.
  • Pointer submenu indicator icon: Trailing chevron that identifies an action row as a nested flyout trigger.
  • Touch sheet frame: BottomSheet panel, content area, handle, and optional scrim that host touch actions.
  • Touch menu surface: Menu-owned content panel rendered inside the touch sheet frame.
  • Touch heading: Current action-sheet title, updated when a nested action view is opened.
  • Touch action list: Spacious List that groups actions in the touch presentation.
  • Touch action row: ListItem button used for an action or drill-in entry in the touch presentation.
  • Touch divider: Divider between action groups in the touch presentation.

An independently checkable option inside DropdownMenu. The row owns menuitemcheckbox semantics while the shared Indicator supplies decorative checkbox paint.

属性类型默认值说明
labelReactNode标识该项的主标签文本。
descriptionReactNode显示在标签下方的次要描述文本。
iconIconType显示在标签前的图标。
valueboolean该项是否被勾选。受控,与 onChange 搭配使用。
onChange(checked: boolean) => void切换该项时以下一个勾选状态触发的回调。
isDisabledbooleanfalse该项是否禁用。
hasCloseOnSelectbooleanfalse切换该项是否关闭菜单。默认保持打开。
refReact.Ref<HTMLElement>转发到行根元素(带 role="menuitemcheckbox" 的元素)的 ref。
endContentReactNode在标签和描述之后渲染的内容。

主题 API

{
  "targets": [
    {
      "className": "astryx-dropdown-menu-item",
      "visualProps": [
        "size"
      ]
    }
  ]
}

组件结构

  • Checkable row: The menuitemcheckbox root that owns the accessible name, checked state, focus, and activation.
  • Label/content: Caller content laid out by Item: the required label plus optional description, icon, and end content.
  • Checkbox marker: Decorative shared Indicator chrome that visualizes checked and disabled state without adding a second control.

A horizontal rule separating groups of rows in a compound menu. Renders role="separator", so it is never a stop in the arrow-key order. The data-driven equivalent is the {type: "divider"} entry in items; both modes render this component, so they look and theme identically.

属性类型默认值说明
xstyleStyleXStylesStyleX styles applied after the menu spacing. Must be a stylex.create() value: not an inline style object like style={{}}.

主题 API

{
  "targets": [
    {
      "className": "astryx-dropdown-menu-divider"
    }
  ]
}

A titled group of rows in a compound menu. Renders role="group" named by its heading; the heading is plain text, not a menuitem, so arrow keys and typeahead skip it. The data-driven equivalent is the {type: "section", title, items} entry in items; the heading shares its typography and theme target.

属性类型默认值说明
titleReactNodeHeading shown above the rows and used as the group's accessible name. Omit for an unnamed group.
childrenReactNodeThe rows of the group: DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuSubMenu.
xstyleStyleXStylesStyleX styles applied to the group wrapper. Must be a stylex.create() value: not an inline style object like style={{}}.

主题 API

{
  "targets": [
    {
      "className": "astryx-dropdown-menu-section-heading"
    }
  ]
}

用于自定义项渲染的辅助组件,提供一致的样式。宿主属性和 DOM 事件处理器(aria-、data-、id、拖放)会落在行根元素上,即带 role="menuitem" 的元素。

属性类型默认值说明
iconIconType显示在标签前的图标。
labelReactNode主标签文本。
descriptionReactNode显示在标签下方的次要描述文本。
onClick(event: MouseEvent) => void选中该项时的回调。键盘激活会以携带修饰键的合成点击到达;带 href 的行会在浏览器导航前运行,带修饰键的点击则交给浏览器处理。
hrefstring该行导航到的地址。此时该行渲染为带 role="menuitem" 的真实链接,带修饰键的点击或中键点击保留浏览器语义(新标签页)。
target'_blank' | '_self'链接目标。仅与 href 一起使用。
relstring链接关系。target="_blank" 时自动加入 noopener noreferrer。
endContentReactNode在标签和描述之后渲染的附加内容。
hasCloseOnSelectbooleantrue激活该项时是否关闭菜单。若操作要在该项上就地反馈结果,请设为 false。
variant'default' | 'destructive''default'视觉变体。'destructive' 会以错误色渲染标签、描述和图标,用于危险操作(如删除)。
xstyleStyleXStyles根容器的 StyleX 样式。
refReact.Ref<HTMLElement>转发到行根元素(带 role="menuitem" 的元素)的 ref。

单选的单选菜单项组(menuitemradio 的 role="group")。持有所选值,并以菜单的项间距布局其子项。

属性类型默认值说明
valuestring | undefined组中当前选中的值。尚无选中项时传 undefined。
onChange(value: string) => void所选值变化时触发的回调。
labelstring组的无障碍名称,作为 aria-label 应用,以便屏幕阅读器将单选项作为命名集合朗读,例如“Sort by”。必需。当名称已作为可见元素存在时,可改用 aria-labelledby(通过基础属性)。
hasCloseOnSelectbooleantrue选择某值是否关闭菜单。默认关闭。
childrenReactNode组成该组的 DropdownMenuRadioItem。

DropdownMenuRadioGroup 中的单个选项(role="menuitemradio")。必须在 DropdownMenuRadioGroup 内使用。

属性类型默认值说明
valuestring该项在组内代表的值。组的 value 与之匹配以确定勾选状态。
labelReactNode标识该选项的主标签文本。
descriptionReactNode显示在标签下方的次要描述文本。
iconIconType显示在标签前的图标。
isDisabledbooleanfalse该单选项是否禁用。
endContentReactNode在标签和描述之后渲染的内容。
refReact.Ref<HTMLElement>转发到行根元素(带 role="menuitemradio" 的元素)的 ref。

A single menu row that reveals a nested flyout of its own items. The row adopts DropdownMenuItem semantics (label / icon / description / isDisabled); its children become the flyout content. Opens inline-end with viewport auto-flip; Right/Enter/Space opens and focuses the first item, Left/Escape closes and returns focus to the trigger (Right/Left swap in RTL). On a phone (a coarse pointer, decided when the menu opened) the row drills in instead: its rows replace the menu's rows in the same box, led by a Back row named "Back to <parent>", and Back, Escape or ArrowLeft return to the row. Host attributes and DOM event handlers (aria-, data-, id) reach the trigger row, the element carrying role="menuitem" and aria-haspopup="menu"; a caller's handlers run after the row's own, except for the keys that open the flyout (the open arrow, Enter, Space) and a click on a disabled row. For data-driven menus, give a menu item a nested items array instead of using this component directly.

属性类型默认值说明
labelReactNodePrimary label text for the trigger row.
iconIconTypeIcon to display before the label. See astryx docs icons for valid semantic names.
descriptionReactNodeSecondary description text displayed below the label.
childrenReactNodeThe flyout menu items: the same components used at the top level (DropdownMenuItem, nested DropdownMenuSubMenu, selectable items).
isDisabledbooleanfalseA disabled submenu renders its trigger row but never opens the flyout.
hasSpinnerbooleanfalseShow a spinner in place of the caret, e.g. while a lazy submenu's children are loading.
menuWidthnumber | stringMinimum flyout width. The flyout may grow for its content, but it is capped to the available viewport space. Defaults to intrinsic sizing (min 160px).
onOpenChange(isOpen: boolean) => voidCalled when the flyout opens or closes.
presentation'flyout' | 'drill-in' | 'adaptive''adaptive'How the sub-menu shows its rows. 'flyout' opens them beside the row; 'drill-in' replaces the menu's rows with them and a Back row, in the same box; 'adaptive' drills in when a finger opened the menu (a coarse pointer, decided when the menu opened) and flies out otherwise.
xstyleStyleXStylesStyleX styles for the trigger row. Must be a stylex.create() value: not an inline style object like style={{}}.
refReact.Ref<HTMLElement>Ref forwarded to the trigger row, the element carrying role="menuitem" and aria-haspopup="menu".

完整示例的额外依赖

部分示例使用独立图标包。示例清单中的每条安装命令包含该示例实际需要的依赖;若要运行本页全部示例,还需安装:

npm install @heroicons/react@2.2.0