TxDropdownSubmenu nests another panel inside the menu, at any depth. Hovering the trigger row expands it; pointer travel between the parent and child panels never closes the chain by mistake, nor does a diagonal path across sibling rows expand them, and selecting a TxDropdownItem inside the child closes the whole chain following the root menu's closeOnSelect.
- Keep dropdowns to a short command list. If the panel needs paragraphs, forms, or multiple interaction steps, use
TxPopover, TxDrawer, or TxContextMenuPanel composition instead. - Use
danger only for destructive commands and keep those commands visually separated from neutral actions when the list grows. - Use
arrow for navigation/submenu rows; use the right slot for external-link icons, keyboard hints, or status badges. - Leave
closeOnSelect=true for normal commands. Set it to false only when the row opens another surface or starts a multi-step flow. - The default
activationFeedback=true clears the highlight for 90 ms, confirms with the existing active state for 90 ms, then emits select and closes. Disable it only when the host already supplies stronger immediate feedback or requires synchronous callbacks; reduced motion automatically uses the immediate path. - Set
initialFocus="none" only when the host places focus itself after opening (focusing a search field inside the panel, for example). Otherwise keyboard users open the menu with focus still on the trigger and need an extra arrow press to reach the list. - Prefer
panelBackground="refraction" / default panel styling unless a surrounding surface already provides strong contrast.
| Prop | Type | Default | Description |
|---|
modelValue | boolean | undefined | Controls whether the dropdown is open (v-model). Omit it for uncontrolled open state, kept internally. |
trigger | 'click' | 'hover' | 'click' | How the trigger opens the menu, forwarded to TxPopover. It owns hover handling for both the reference and the panel, so timing and mutual exclusion come from the shared delay service rather than a close timer in the host. |
placement | DropdownPlacement | 'bottom-start' | Positions the panel relative to the trigger. |
offset | number | 6 | Distance in pixels between the trigger and panel. |
closeOnSelect | boolean | true | Closes the menu after an enabled item emits select. |
activationFeedback | boolean | true | Runs a 90 ms clear + 90 ms confirmation before select and close for closing commands; reduced motion skips it. |
initialFocus | 'first-item' | 'none' | 'first-item' | Where focus lands when the menu opens. 'first-item' focuses the first enabled item; 'none' leaves focus alone so the host can place it itself (a search field at the top of the panel, for example). Arrow keys still hand focus from there to the list. |
animation | BaseAnchorAnimationOptions | {} | Popover animation config forwarded to BaseAnchor; an empty object uses BaseAnchor's default animation. |
minWidth | number | 220 | Minimum panel width in pixels. The maximum panel width is fixed at 360px, with no prop to change it. |
maxHeight | number | 420 | Maximum scrollable panel height in pixels. |
unlimitedHeight | boolean | false | Disables the panel max-height constraint. |
referenceClass | BaseAnchorClassValue | - | Extra class value forwarded to the trigger anchor. |
panelCard | BaseAnchorPanelCardProps | - | Low-level card props forwarded to the popover panel. |
panelVariant | 'solid' | 'dashed' | 'plain' | 'solid' | Visual border variant forwarded to the popover panel. |
panelBackground | 'pure' | 'mask' | 'blur' | 'glass' | 'refraction' | 'refraction' | Background treatment forwarded to the popover panel. |
panelShadow | 'none' | 'soft' | 'medium' | 'soft' | Shadow strength forwarded to the popover panel. |
panelRadius | number | 18 | Panel corner radius in pixels. |
panelPadding | number | 8 | Panel padding in pixels. |
| Prop | Type | Default | Description |
|---|
disabled | boolean | false | Prevents selection and renders the item as disabled. |
danger | boolean | false | Applies danger text styling for destructive actions. |
arrow | boolean | false | Shows a trailing chevron when no right slot is provided. |
closeOnSelect | boolean | undefined | Per-item override of the menu-level closeOnSelect; unset follows the menu-level value. |
activationFeedback | boolean | undefined | Per-item override of the menu-level confirmation feedback; unset follows the menu-level value. |
| Prop | Type | Default | Description |
|---|
disabled | boolean | false | Disables the trigger row; the child panel no longer opens. |
placement | DropdownPlacement | 'right-start' | Positions the child panel relative to the trigger row. |
offset | number | 4 | Distance in pixels between the trigger row and the child panel. |
width | number | 0 | Fixed child panel width; 0 sizes to content bounded by minWidth. |
minWidth | number | 160 | Minimum child panel width in pixels. |
maxHeight | number | 420 | Maximum scrollable child panel height in pixels. |
unlimitedHeight | boolean | false | Disables the child panel max-height constraint. |
animation | BaseAnchorAnimationOptions | {} | Anchor animation config for the child panel. |
panelCard | BaseAnchorPanelCardProps | - | Low-level card props forwarded to the child panel. |
panelVariant | 'solid' | 'dashed' | 'plain' | 'solid' | Visual border variant of the child panel. |
panelBackground | 'pure' | 'mask' | 'blur' | 'glass' | 'refraction' | 'refraction' | Background treatment of the child panel. |
panelShadow | 'none' | 'soft' | 'medium' | 'soft' | Shadow strength of the child panel. |
panelRadius | number | 14 | Child panel corner radius in pixels. |
panelPadding | number | 6 | Child panel padding in pixels. |
| Event | Params | Description |
|---|
update:modelValue | (value: boolean) | Emitted when the menu requests an open-state change. |
open | - | Emitted when the menu requests opening. |
close | - | Emitted when the menu requests closing. |
| Event | Params | Description |
|---|
select | - | Emitted when an enabled item activates. Closing items with feedback enabled emit after the 180 ms confirmation; bypass paths emit immediately. |
Admin pages should not put every setting into one large form. Use TxTabs for first-level sections, TxDropdownMenu for lightweight actions in the current section, TxPopover for short explanations, and TxDrawer for dense configuration.
| Slot | Props | Description |
|---|
trigger | - | Trigger content forwarded to the Popover reference slot. |
default | - | Menu rows, usually TxDropdownItem children, rendered inside the menu panel. |
| Slot | Props | Description |
|---|
default | - | Main menu item label. |
right | - | Replaces the generated trailing chevron used by arrow. |
| Slot | Props | Description |
|---|
default | - | Main trigger row label. |
right | - | Trailing metadata on the trigger row, rendered before the submenu chevron. |
menu | - | Child panel content, usually TxDropdownItem rows or nested TxDropdownSubmenu. |
- Menu rows use the same outlined hover and active states as every other list row in the library. They previously opted out for a translucent veil; a menu highlighting differently from a select, a tree or a cascader was the more confusing of the two.
TxDropdownMenu is for short action lists; use TxPopover / TxDrawer when content includes explanations, forms, or complex state.TxDropdownMenu wraps TxPopover, so placement, offset, height limiting, panel card props, and animation follow the same anchor behavior.closeOnSelect closes the parent after an enabled item completes confirmation and emits select; disabled items do not select or close, while closeOnSelect=false submenu/multi-step rows select immediately without blinking.activationFeedback is enabled by default: the row clears its current hover/focus highlight, reuses TxCardItem's active style to confirm, then runs the business action and closes. prefers-reduced-motion: reduce, menu/item opt-out, or a missing parent menu context adds no delay.- The menu panel uses
role="menu", and each item uses role="menuitem" through TxDropdownItem. - The first enabled item receives focus when the menu opens;
initialFocus="none" skips that step so the host can place focus itself (a search field at the top of the panel, for example). - Inside the panel,
ArrowDown / ArrowUp move focus between enabled items with wraparound and enter the list from a non-item such as a search field; Home / End jump to the first / last item, skipping aria-disabled items, except when focus is inside an input, textarea, or contenteditable element, where they pass through and move the caret. TxDropdownItem emits select with no payload; attach business context in the handler that renders the item.- The
TxDropdownSubmenu trigger row is a role="menuitem": ArrowRight / Enter expand the child panel and focus its first item, and ArrowLeft inside the child panel collapses it and returns focus to the trigger row. - Pointer travel from parent panel into the child panel never closes the parent; clicks inside the child panel don't count as outside-clicks for the parent, and closing the parent (including preemption by another menu) cascades to its children.
- A diagonal path from the trigger row to the child panel rides the safe triangle: sibling submenu rows it crosses do not expand and the open child panel stays; resting on a sibling row for about 100ms switches to it. The 4px gap between the panels is covered by the hover bridge.
- Source:
packages/tuffex/packages/components/src/dropdown-menu/src/TxDropdownMenu.vue forwards Popover placement, sizing, animation, and panel visual props, then injects closeOnSelect and activationFeedback into items. - Source:
packages/tuffex/packages/components/src/dropdown-menu/src/TxDropdownItem.vue confirms disabled/danger/arrow/closeOnSelect/activationFeedback props, right slot behavior, role="menuitem", and the pre-close confirmation. - Source:
packages/tuffex/packages/components/src/dropdown-menu/src/TxDropdownSubmenu.vue confirms hover expansion, keyboard traversal, and root-context passthrough (selecting a nested item closes the whole chain); its closeOnSelect=false trigger never runs pre-close feedback. - Shared behavior:
packages/tuffex/packages/utils/menu-activation-feedback.ts owns the 90 ms clear / 90 ms confirm state machine, reduced-motion bypass, duplicate suppression, and unmount cleanup. - Type contracts:
packages/tuffex/packages/components/src/dropdown-menu/src/types.ts exports DropdownMenuProps, DropdownItemProps, the injected menu context, and the supported placement union. - Keyboard contract:
Home / End pass through input, textarea, and contenteditable targets because a search field inside the panel needs them to move the caret; a menu that takes them leaves the field uneditable. Arrow keys are still claimed on editable targets on purpose, since that is the only way the field hands focus to the list. Asking hosts to stopPropagation on the field was rejected: it would copy the exception into every host. - Verified coverage: Coverage:
packages/tuffex/packages/components/src/dropdown-menu/__tests__/dropdown-menu.test.ts covers prop forwarding, open/close events, pointer and keyboard confirmation timing, duplicate suppression, menu/item opt-out, reduced motion, unmount cleanup, disabled items, closeOnSelect=false, initialFocus="none", and the input/contenteditable navigation exceptions.
查看源码packages/tuffex/packages/components/src/dropdown-menu/index.ts