Skip to content

Adjust active index when Menu items and Listbox options are unregistered - #3918

Open
atsui-affinity wants to merge 2 commits into
tailwindlabs:mainfrom
atsui-affinity:fix-menu-listbox-unregister-active-index
Open

atsui-affinity wants to merge 2 commits into
tailwindlabs:mainfrom
atsui-affinity:fix-menu-listbox-unregister-active-index

Conversation

@atsui-affinity

@atsui-affinity atsui-affinity commented Nov 9, 2026 •

Copy link
Copy Markdown

Fixes #3856
An item or option can be removed while a Menu or Listbox is open. The UnregisterItems and UnregisterOptions reducers then remove it from the array. They do not change activeItemIndex or activeOptionIndex. The index now points past the end of the array. The next Enter throws Cannot destructure property 'dataRef' of ... as it is undefined.

Combobox already avoids this because its unregister reducer calls adjustOrderedState(). This PR makes the Menu and Listbox reducers do the same. If the active item was removed, the active index becomes null. If an earlier item was removed, the index moves so the same item stays active.

This is a smaller take on #3857. It reuses the existing helper and adds no null guards, because the reducer fix makes them unreachable.

@headlessui/vue is not affected. Its unregisterItem already uses adjustOrderedState.

How to reproduce issue

Minimal React Code

Here is a minimal repro, trimmed from the demo page I recorded. It uses @headlessui/react 2.2.10 and React 18 or 19.

To trigger it:

  1. Click "Remove last 3 items in 3s".
  2. Open the menu and press End, so the last item ("Billing") is active.
  3. Wait for the items to disappear.
  4. Press Enter.
import { Menu } from '@headlessui/react'
import { useState } from 'react'

export function Repro() {
  const [removed, setRemoved] = useState(false)
  const items = ['Profile', 'Settings', 'Admin console', 'Billing']
  const visible = removed ? items.slice(0, 1) : items

  return (
    <>
      <button onClick={() => setTimeout(() => setRemoved(true), 3000)}>
        Remove last 3 items in 3s
      </button>

      <Menu>
        <Menu.Button>Open menu</Menu.Button>
        <Menu.Items>
          {visible.map((name) => (
            <Menu.Item key={name}>
              {({ focus }) => (
                <button style={{ background: focus ? '#dbeafe' : 'transparent' }}>{name}</button>
              )}
            </Menu.Item>
          ))}
        </Menu.Items>
      </Menu>
    </>
  )
}

You get Uncaught TypeError: Cannot destructure property 'dataRef' of 'machine.state.items[machine.state.activeItemIndex]' as it is undefined. The menu stays open and Enter does nothing.

The timer stands in for real cases, such as a permission change or a filter that removes items while the menu is open. The Listbox version is the same, with Listbox.Option in place of Menu.Item.

Video

Before - error appears in menu item

headlessui-menu-before-crash.mp4

After - fixed menu

headlessui-menu-after-fixed.mp4

Tests

Two new tests each for Menu and Listbox:

  • The active item is removed. No item is active afterwards, and Enter closes the menu.
  • An item before the active one is removed. The same item stays active.

All four fail without the fix and pass with it. The full React component suite, tsc and prettier pass.

Delegate the unregister reducers to adjustOrderedState, matching Combobox, so
removing the active item no longer leaves a stale index that crashes on Enter.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 9, 2026

Copy link
Copy Markdown

@atsui-affinity is attempting to deploy a commit to the Tailwind Labs Team on Vercel.

A member of the Team first needs to authorize it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@atsui-affinity
atsui-affinity marked this pull request as draft November 9, 2026 16:51
@atsui-affinity
atsui-affinity marked this pull request as ready for review November 9, 2026 17:03

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Menu and Listbox crash when items are dynamically removed while active

1 participant