Skip to content

DropdownMenuTrigger should render a native button and avoid nested interactive controls #9

Open

Description

@JoaoTMDias

Problem

DropdownMenuTrigger currently renders a focusable <span role="button"> instead of a native <button>.

This makes the trigger responsible for manually reproducing button behaviour and allows consumers to create invalid nested interactive markup:

<DropdownMenuTrigger>
  <Button variant="outline">Open menu</Button>
</DropdownMenuTrigger>

This can cause accessibility issues with semantics, keyboard interaction, focus management, and screen reader announcements.

Expected behaviour

DropdownMenuTrigger should render exactly one native <button> and own the dropdown trigger behaviour.

Consumers should be able to write:

<DropdownMenuTrigger variant="outline">
  Open menu
</DropdownMenuTrigger>

The component should support the existing Button visual options and icon slots while preserving accessible naming attributes such as aria-label.

Proposed solution

  • Render the existing Button component internally.
  • Always use type="button".
  • Preserve native button activation for click, Enter, and Space.
  • Support ArrowUp and ArrowDown opening behaviour.
  • Respect disabled and event.defaultPrevented.
  • Keep dropdown-owned state and relationship attributes controlled by the runtime.
  • Preserve existing closing, selection, submenu, positioning, and focus restoration behaviour.
  • Migrate existing nested-Button consumers and documentation examples.

Acceptance criteria

  • The trigger renders one native <button> with no nested interactive controls.
  • Accessible names and dropdown relationship/state attributes are correct.
  • Click, Enter, and Space activate the menu exactly once.
  • ArrowUp opens on the last enabled item; ArrowDown opens on the first.
  • Disabled triggers do not open.
  • Escape and item selection restore focus correctly.
  • Existing submenu, outside-click, Tab, and positioning behaviour remains unchanged.
  • Registry dependencies, documentation, and examples are updated.

Breaking change

Existing consumers using a nested Button must move Button props and slot content directly to DropdownMenuTrigger.

This change should be reviewed as a breaking API change.

Related work

A draft implementation is available in PR #8.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions