Skip to content

ColorField: typed value is not committed on Enter and silently discarded on Escape dismissal #10445

Description

@grayashh

Provide a general summary of the issue here

Typing a hex value into ColorField (inside a ColorPicker popover) and pressing Enter does not commit the typed value — the field's internal inputValue is only flushed to the color state on blur. If the user then closes the popover with Escape, the typed value is silently discarded and the previous color is restored, so users believe they picked a color they never actually committed.

🤔 Expected Behavior?

Pressing Enter in a ColorField commits the typed value, the same way NumberField does — useNumberField explicitly handles Enter and commits via flushSync(() => commit()) (packages/react-aria/src/numberfield/useNumberField.ts:261-267 on main).

😯 Current Behavior

useColorField wires commit to onBlur only (packages/react-aria/src/color/useColorField.ts:147 on main) and has no Enter handling. So:

  1. Type #889096 into the hex field (previous color #3B82F6)
  2. Press Enter → the input keeps displaying #889096, but the picker state (swatch, sliders, onChange) still holds #3B82F6
  3. Press Escape to close the popover → color silently reverts to #3B82F6

Tabbing out of the field (blur) commits correctly.

This is the keyboard sibling of #9748 ("ColorPicker's color field doesn't commit its value if the popover is dismissed via outside click"). That one was fixed by #9768 (in react-aria@3.51.0) for the pointer-dismissal path, but the Enter/Escape paths are unaffected since React does not fire blur on unmount.

💁 Possible Solution

Mirror the useNumberField Enter handling in useColorField (commit on Enter, keep focus). Whether Escape should commit or cancel is a separate design question — cancel-on-Escape is conventional, and becomes predictable once Enter commits.

Workaround we ship meanwhile — commit via programmatic blur, refocus to preserve the focus ring (sidesteps the a11y objection from #2596):

<ColorField.Input
  onKeyDown={(e) => {
    if (e.key !== "Enter") return;
    e.preventDefault();
    const input = e.currentTarget;
    input.blur();
    input.focus();
  }}
/>

🔦 Context

Operators picking a category color in a B2B console typed a custom hex, pressed Enter (the conventional "confirm" key for a text input), closed the popover, and saved — persisting the default blue instead of their chosen color, with no feedback that anything was dropped.

🖥️ Steps to Reproduce

Any ColorPicker + ColorField composition reproduces it, e.g. the docs example:

<ColorPicker defaultValue="#3B82F6">
  {/* trigger ... */}
  <Popover>
    <ColorField colorSpace="hsb" aria-label="Hex">
      <Input />
    </ColorField>
  </Popover>
</ColorPicker>

Type a different hex → press Enter → inspect the picker state: unchanged. Press Escape → typed value discarded.

Version

react-aria 3.51.0 / react-aria-components 1.20.0 (also reproduces reading mainuseColorField.ts has no Enter handling)

What browsers are you seeing the problem on?

Chrome (Chromium 139) — behavior is library-level, not browser-specific.

What operating system are you using?

macOS 15

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions