How to prevent auto-focus on first button in dialog?

master
a0f6cf5[internal] Iterate on resolveMultipleLabels in one loop (#3709)
This post might have stale content, as master is 413 commits ahead.

How can I prevent the first button in a dialog to be automatically focused on open?

Avatar of anthropic/claude-opus-4.5
Claude Opus 4.5
commented

I have all the information needed. Here's how to prevent automatic focus in a Dialog:

##Preventing Automatic Focus in Dialog

By default, when a Dialog opens, Base UI focuses the first focusable element inside the popup. You can control this behavior using the initialFocus prop on <Dialog.Popup>.

###Option 1: Disable automatic focus completely

Set initialFocus={false} to prevent moving focus when the dialog opens:

<Dialog.Root>
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Popup initialFocus={false}>
      <button>First Button</button>
      <button>Second Button</button>
    </Dialog.Popup>
  </Dialog.Portal>
</Dialog.Root>

###Option 2: Focus a specific element

Pass a ref to focus a specific element instead of the first one:

function MyDialog() {
  const secondButtonRef = React.useRef(null);
  
  return (
    <Dialog.Root>
      <Dialog.Trigger>Open</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Popup initialFocus={secondButtonRef}>
          <button>First Button</button>
          <button ref={secondButtonRef}>Second Button (focused)</button>
        </Dialog.Popup>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

###Option 3: Conditional focus based on interaction type

Pass a function that receives the interaction type ('mouse', 'touch', 'pen', or 'keyboard') and return:

  • An element to focus
  • true for default behavior
  • false/undefined to do nothing
<Dialog.Popup 
  initialFocus={(interactionType) => {
    if (interactionType === 'keyboard') {
      return someRef.current; // Focus specific element for keyboard users
    }
    return false; // Don't move focus for mouse/touch
  }}
>
  {/* ... */}
</Dialog.Popup>

###Summary of initialFocus values

ValueBehavior
falseDo not move focus
trueDefault behavior (first tabbable element or popup)
RefObjectFocus the referenced element
functionCalled with interaction type, returns element/boolean
Avatar of sgalanb
Santiago Galán
commented

I don't want the buttons to be focused on open but I do want to maintain the focus ability, if the users tabs or move focus in other way it should let them focus the buttons.

Avatar of anthropic/claude-opus-4.5
Claude Opus 4.5
commented

That's exactly what initialFocus={false} does! It only prevents the automatic focus movement when the dialog opens, but it doesn't disable focus capability on the elements inside.

Users can still:

  • Tab into the dialog and focus the buttons
  • Click on buttons to focus them
  • Use any other method to move focus to the buttons

Here's the solution:

<Dialog.Root>
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Popup initialFocus={false}>
      <button>First Button</button>
      <button>Second Button</button>
    </Dialog.Popup>
  </Dialog.Portal>
</Dialog.Root>

With initialFocus={false}, when the dialog opens:

  1. Focus stays on the trigger (or wherever it was before)
  2. The buttons remain fully focusable - users can Tab to them or click them normally
  3. All normal keyboard navigation continues to work

This is confirmed by the test case in the source code, which shows that after opening with initialFocus={false}, the focus simply remains on the trigger rather than being moved into the dialog:

// From DialogPopup.test.tsx
it('should not move focus when initialFocus is false', async () => {
  // ...
  await user.click(trigger);
  await waitFor(() => {
    expect(trigger).toHaveFocus(); // Focus stays on trigger
  });
});
Avatar of sgalanb
Santiago Galán
commented

I did that and since the focus remains on the trigger, if I tab I don't focus the dialog buttons, but other elements under the dialog.

Avatar of anthropic/claude-opus-4.5
Claude Opus 4.5
commented

This reveals the issue. When using a modal dialog (the default, or modal={true} or modal="trap-focus"), focus is trapped inside the dialog. But if you're using modal={false}, focus can escape to elements outside.

Two solutions:

If you want focus to be trapped inside the dialog, make sure you're using a modal dialog (which is the default):

<Dialog.Root modal={true}> {/* or just <Dialog.Root> since true is default */}
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Popup initialFocus={false}>
      <button>First Button</button>
      <button>Second Button</button>
    </Dialog.Popup>
  </Dialog.Portal>
</Dialog.Root>

With a modal dialog, even though initialFocus={false} keeps focus on the trigger initially, pressing Tab will move focus into the dialog because focus is trapped within it.

###Solution 2: Use modal="trap-focus"

If you want focus trapping but still want to allow page scrolling and pointer interactions outside:

<Dialog.Root modal="trap-focus">
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Popup initialFocus={false}>
      <button>First Button</button>
      <button>Second Button</button>
    </Dialog.Popup>
  </Dialog.Portal>
</Dialog.Root>

###Summary of modal options:

ValueFocus TrappedScroll LockedOutside Pointer Disabled
true (default)
"trap-focus"
false

Are you currently using modal={false}? If so, switching to modal={true} or modal="trap-focus" should fix the tabbing issue while still respecting your initialFocus={false} preference.


END OF POST

How to prevent auto-focus on first button in dialog? — mui/base-ui