mui / mui/base-ui

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

Open
#5,366 0 comments 0 reactions 0 assignees View on GitHub
component: toolbar status: waiting for maintainer
Dominant language
TypeScript
Stars
10.9k
Forks
543
Avg merge
1d 20h
Merged PRs (30d)
101

Description

# 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

```ts
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 ``; `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):

```jsx

```

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.Input`… `useFocusableWhenDisabled` 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.

Contributor guide

Open the contributing guide

Research direction

Start with packages/react/src/utils/useFocusableWhenDisabled.ts and the Toolbar.Input implementation, then read ToolbarInput.test.tsx. Reproduce the disabled-input behavior with ordinary typing and IME composition, and check how focusability is preserved. Done means disabled Toolbar.Input rejects all text-entry paths, including composition, with regression coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
accessibility, frontend, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.