nuxt / nuxt/ui

USelectMenu prevents programmatic focus change after selection

Open
#6,506 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
TypeScript
Stars
6.9k
Forks
1.1k
Avg merge
1d 7h
Merged PRs (30d)
57

Description

Environment
  • Operating System: Windows 10.0.26200
  • Node Version: v24.14.0
  • Nuxt Version: 4.4.6
  • CLI Version: 3.35.2
  • Nitro Version: 2.13.4
  • Package Manager: npm 11.9.0
  • Builder: vite 7.3.3
  • User Config: compatibilityDate, css, devtools, eslint, experimental, icon, modules, runtimeConfig, ssr, typescript, vite
  • Modules: @nuxt/eslint 1.15.2, @nuxt/image 2.0.0, @nuxt/ui 4.7.1, @vueuse/nuxt 14.2.1
Is this bug related to Nuxt or Vue?

Nuxt

Reproduction

https://play.ui.nuxt.com/#eNptUbFSwzAM/RVdlsJd0xwcUy5tKUwMLBQmwuDaSuurY+diO9Ar/Xdkp2k6kCWW3tOT9HRMVk0z6zwmeVJY3srGgUXnG1BMb+dl4myZLEot68a0Do7QYjUFjT/uXfI9nKBqTQ0TEpiUutTcaBsEFHKHAuaBXmhfb7CFX/BaYCU1isXN7chlLd+9YdWTAzBAO6aFwmf6bZFQZg+aw80tzBdwLDUA+2bSXWbpKwGyDFbOYU2LOAO16RAqw70N2KXZrGPK43ImdeMdxctZ5ASNU6mLrHeC9qYgaCnmkCKAQsgOuGLWkje2YRzTQ/oQLQL6io913P0Vte8zAF1aG4Eq8M++lMmA5ZLUg9TnkAHyWLENqhwmq8kU4qA53MFp+h/laaTcA43e419jh0hM93igJvE9QrHuDMX3CPVGsY3CIfPoG0Em5HGXNNKp7PpEQ3UWbevdeAn+DhJ03mjC+QZjN7KX484ogS0R1jvjlSA2R3k53pU26WZ0BXoV2eU2yekP+CPm4Q==

Description

When using USelectMenu, it is difficult to programmatically move focus to another input after selecting a value.
The USelectMenu appears to retain or reapply focus after selection, overriding attempts to focus another element; unless I add a time out of at least 300ms (which I'm assuming is enough time for the USelectMenu to give up focus)

Expected behaviour

After selecting an item in USelectMenu, focus should move to the target input when focus() is called.

Actual behaviour
  • focus() is called, but focus remains on (or is returned to) the USelectMenu
  • the input does not receive focus, unless a delay (setTimeout ~300ms+) is introduced
Reka UI behaviour

Upon doing the same on Reka-UI, it seems to work correctly.

Reproduction

<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ComboboxAnchor, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxRoot, ComboboxSeparator, ComboboxTrigger, ComboboxViewport } from 'reka-ui'
import { ref, watch } from 'vue'

const selected = ref('')
const nextInput = ref<HTMLInputElement | null>(null)


const options = [
  { name: 'Fruit', children: [
    { name: 'Apple' },
    { name: 'Banana' },
    { name: 'Orange' },
    { name: 'Honeydew' },
    { name: 'Grapes' },
    { name: 'Watermelon' },
    { name: 'Cantaloupe' },
    { name: 'Pear' },
  ] },
  { name: 'Vegetable', children: [
    { name: 'Cabbage' },
    { name: 'Broccoli' },
    { name: 'Carrots' },
    { name: 'Lettuce' },
    { name: 'Spinach' },
    { name: 'Bok Choy' },
    { name: 'Cauliflower' },
    { name: 'Potatoes' },
  ] },
]

watch(selected, (val) => {
  if (val) {
    nextInput.value?.focus()
  }
})
</script>

<template>
  <div>
    <ComboboxRoot
      class="relative"
      v-model="selected"
    >
      <ComboboxAnchor class="min-w-[160px] inline-flex items-center justify-between rounded-lg border px-[15px] text-xs leading-none h-[35px] gap-[5px] bg-white text-grass11 hover:bg-stone-50 shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-grass9 outline-none">
        <ComboboxInput
          class="!bg-transparent outline-none text-grass11 h-full selection:bg-grass5 placeholder-stone-400"
          placeholder="Placeholder..."
        />
        <ComboboxTrigger>
          <Icon
            icon="radix-icons:chevron-down"
            class="h-4 w-4 text-grass11"
          />
        </ComboboxTrigger>
      </ComboboxAnchor>

      <ComboboxContent class="absolute z-10 w-full mt-1 min-w-[160px] bg-white overflow-hidden rounded-lg shadow-sm border will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade">
        <ComboboxViewport class="p-[5px]">
          <ComboboxEmpty class="text-mauve8 text-xs font-medium text-center py-2" />

          <template
            v-for="(group, index) in options"
            :key="group.name"
          >
            <ComboboxGroup>
              <ComboboxSeparator
                v-if="index !== 0"
                class="h-[1px] bg-grass6 m-[5px]"
              />

              <ComboboxLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
                {{ group.name }}
              </ComboboxLabel>

              <ComboboxItem
                v-for="option in group.children"
                :key="option.name"
                :value="option.name"
                class="text-xs leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-grass9 data-[highlighted]:text-grass1"
              >
                <ComboboxItemIndicator
                  class="absolute left-0 w-[25px] inline-flex items-center justify-center"
                >
                  <Icon icon="radix-icons:check" />
                </ComboboxItemIndicator>
                <span>
                  {{ option.name }}
                </span>
              </ComboboxItem>
            </ComboboxGroup>
          </template>
        </ComboboxViewport>
      </ComboboxContent>
    </ComboboxRoot>
    <input
      ref="nextInput"
      class="border px-2 h-[35px] rounded"
    />
  </div>
</template>

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked play reproduction and inspect USelectMenu's selection and focus behavior. Reproduce the Vue example where nextInput.focus() is called after selection, then trace the relevant component entry point. Done means focus moves to the target input without a timeout.

Written by the indexing model from the issue text.

Assessment

Tech stack
nuxt, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
56/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.