[toolbar] Disabled Toolbar.Input still accepts IME composition text
- 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
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