Skip to content

[toolbar] Disabled Toolbar.Input still accepts IME composition text #5366

Description

@LEECHANHYUNG

Bug report

Current behavior

A disabled Toolbar.Input still accepts text entered through an IME (Korean, Japanese, Chinese, etc.). Typing with a Latin keyboard is correctly blocked, but composing text with an IME writes into the input as if it were enabled.

The root cause is in useFocusableWhenDisabled, which blocks text entry solely by cancelling keydown:

https://github.com/mui/base-ui/blob/master/packages/react/src/utils/useFocusableWhenDisabled.ts#L22-L24

onKeyDown(event: React.KeyboardEvent) {
  if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
    event.preventDefault();
  }
}

Toolbar.Input passes focusableWhenDisabled (default true) and isNativeButton: false, so the native disabled attribute is never applied — only aria-disabled. The repo's own test asserts this (ToolbarInput.test.tsx: expect(input).not.toHaveAttribute('disabled')). ToolbarInput itself only cancels onClick and onPointerDown, so that single preventDefault is the only thing preventing text entry.

That is not enough: an IME commits composed text through a channel that keydown cancellation never reaches.

Importantly, preventDefault is not failing to run. I measured it being called, and it does correctly block both ordinary typing and text carried on the key event itself. Only the composition → commit path gets through.

Measurements

Chromium, driven over CDP with Input.dispatchKeyEvent (virtual keycode 229, as a real IME emits) followed by Input.imeSetComposition + Input.insertText. Each variant is a bare <input>; prevented reproduces useFocusableWhenDisabled's handler verbatim.

variant IME (compose after cancelled keydown) text on key event ordinary typing
control (no handler) abc
prevented (current behavior) ← bug '' ''
native disabled attribute '' '' ''
readOnly '' '' ''

The control row confirms the simulation is faithful; the prevented row is the bug.

Expected behavior

A disabled Toolbar.Input should not accept text from any input method, including IME composition.

Reproducible example

Render a disabled Toolbar.Input, focus it, and type with an IME (e.g. Korean 2-set):

<Toolbar.Root>
  <Toolbar.Input disabled defaultValue="" />
</Toolbar.Root>

The composed characters appear in the input.

Standalone script reproducing the mechanism headlessly (no React needed, ~1s, exits non-zero on the bug) — happy to open it as a gist or fold it into a test if useful.

Base UI version

v1.6.0

Which browser are you using?

Chrome (measured). Not yet verified in Safari or Firefox — worth checking, since NumberFieldInput already notes that isComposing is broken in Safari (webkit bug 165004).

Which OS are you using?

macOS

Which assistive tech are you using (if applicable)?

N/A

Additional context

Suggested fix

Marking the input readOnly while disabled blocks every path above while keeping the element focusable, so focusableWhenDisabled behavior (tabbing away, roving focus) is preserved. The native disabled attribute also blocks IME but removes focusability, defeating the purpose of the hook.

Scoping it to ToolbarInput looks sufficient: useFocusableWhenDisabled has only two consumers (ToolbarInput and useButton), and isNativeButton: false is only used by ToolbarInput — it is the only text-entry consumer. Applying readOnly in the shared hook would leak the attribute onto buttons and links.

As a bonus, readOnly also closes context-menu paste, drag-and-drop, and autofill, which currently bypass the disabled state through the same gap.

Note that readOnly is ignored by checkboxes, so Toolbar.Input type="checkbox" would keep relying on the existing onClick/onPointerDown handling — not an IME concern.

Relationship to #4968

#4968 states: "Removed the redundant stopEvent keydown handler from Toolbar.InputuseFocusableWhenDisabled already prevents text entry." This report contradicts that premise for IME input, so the fix would partially revisit that decision. Flagging it explicitly so the history is clear.

Why the existing keyCode === 229 guards don't apply

The repo has event.which === 229 guards in NumberFieldInput, ComboboxInput, and useListNavigation, but those exist to avoid interfering with IME composition — the opposite of what's needed here. Adding such a guard makes no difference to this bug; the composition commit path still writes to the input.

Test coverage gap

ToolbarInput.test.tsx currently contains no keyDown, preventDefault, or composition assertions, so "typing is blocked while disabled" is untested in general, not just for IME.

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: toolbarChanges related to the toolbar component.status: waiting for maintainerThese issues haven't been looked at yet by a maintainer.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions