Why Your Input Loses Focus Inside a Radix Context Menu (And How to Fix It)

Introduction
If you ever tried to put an editable input next to a Radix UI context menu. You probably hit this wall. The input loses focus. It refuses to stay focused. You click on it. Nothing happens. Or worse. It focuses for a split second then blurs immediately.
This is not a random bug. This is Radix doing exactly what it was designed to do.
What Radix Does Under the Hood
Radix UI components like ContextMenu and DropdownMenu use something called a focus trap. A focus trap means: when a menu opens. Focus is locked inside that menu. You can only tab between menu items. Nothing outside the menu can receive focus. When the menu closes. Focus returns to the trigger element. The element that opened the menu.
This is great for accessibility. Screen readers and keyboard users need this behavior. The problem starts when your trigger element contains an input that needs to stay focused after the menu closes.
The Real Problem: onCloseAutoFocus
When a Radix context menu closes. It fires an internal event called onCloseAutoFocus. This event moves focus back to the trigger. If your input lives inside or near the trigger. Radix will yank focus away from it. Every single time.
Here is the fix. You need to prevent that default behavior.
<ContextMenuContent
onCloseAutoFocus={(event) => {
event.preventDefault();
}}
>
{/* menu items */}
</ContextMenuContent>
By calling event.preventDefault() you tell Radix: do not move focus anywhere when the menu closes. Now you control where focus goes.
The Sneaky Blur Bug
There is a second problem that is harder to find. When a Radix context menu is open and you hover over a menu item. Radix gives that item focus. This causes your input to fire a blur event. Your code sees the blur. Thinks the user is done editing. And exits edit mode.
But the user did not leave the input on purpose. They just hovered a menu item by accident.
The fix is to check what caused the blur. Radix menu items have a special attribute called data-radix-collection-item. You can use this to detect if the blur was caused by a Radix menu item.
const handleBlur = (event: React.FocusEvent) => {
const isRadixMenuItem = event.relatedTarget?.hasAttribute(
"data-radix-collection-item",
);
if (!event.relatedTarget || !isRadixMenuItem) {
// Real blur. Save the input and exit edit mode.
handleSave();
} else {
// Fake blur caused by Radix. Re-focus the input.
requestAnimationFrame(() => {
inputRef.current?.focus();
inputRef.current?.select();
});
}
};
The relatedTarget property tells you which element received focus instead. If that element is a Radix menu item. You know the blur is not real. So you push focus back to the input on the next frame using requestAnimationFrame.
Why requestAnimationFrame Matters
You will see requestAnimationFrame a lot when dealing with Radix focus issues. The reason is simple. React state updates are asynchronous (meaning they do not happen instantly). When you call setIsEditing(true) the input does not exist in the DOM yet. It will appear after the next render.
If you try to call inputRef.current.focus() right after setIsEditing(true). The input is not there yet. The focus call does nothing.
requestAnimationFrame tells the browser: wait until the next paint. By then. React has finished rendering. The input exists. Now you can focus it.
function startRenaming() {
setIsEditing(true);
requestAnimationFrame(() => {
inputRef.current?.focus();
inputRef.current?.select();
});
}
For the initial render of a new item. useLayoutEffect works better than requestAnimationFrame. It fires synchronously after the DOM updates but before the browser paints. This prevents any visible flash where the input appears unfocused.
useLayoutEffect(() => {
if (isNew) {
inputRef.current?.focus();
inputRef.current?.select();
}
}, [isNew]);
Summary
Three things to remember when combining editable inputs with Radix menus.
1. Prevent auto focus on close. Use onCloseAutoFocus with event.preventDefault() on the menu content. Otherwise Radix will steal focus from your input.
2. Guard your blur handler. Check event.relatedTarget for data-radix-collection-item. If a Radix menu item caused the blur. Ignore it and re-focus your input.
3. Delay your focus calls. Use requestAnimationFrame after state changes. Use useLayoutEffect for initial mount. React needs time to render the input before you can focus it.
These are small fixes. But without them you can spend hours wondering why your input refuses to cooperate. The root cause is always the same. Radix manages focus aggressively for accessibility. And you need to work with that system. Not against it.






