Skip to content

CUI-5: Prevent implicit form submission on Enter for non-text elements#1014

Open
JeanMarcMilletScality wants to merge 2 commits into
development/1.0from
bugfix/CUI-5-select-enter-prevents-default
Open

CUI-5: Prevent implicit form submission on Enter for non-text elements#1014
JeanMarcMilletScality wants to merge 2 commits into
development/1.0from
bugfix/CUI-5-select-enter-prevents-default

Conversation

@JeanMarcMilletScality
Copy link
Copy Markdown
Contributor

@JeanMarcMilletScality JeanMarcMilletScality commented Feb 25, 2026

Summary

Pressing Enter on a Select inside any <form> triggered the browser's implicit form submission instead of being consumed by the combobox. Fixed by wrapping the Select's react-select instance in a display: contents div with an onKeyDown handler that calls preventDefault() on Enter. The fix lives in the widget itself, so it works regardless of which form container the Select is in.

Ticket: https://scality.atlassian.net/browse/CUI-5

Changes

  • Selectv2.component.tsx: wrap react-select in a display: contents div that preventDefaults Enter
  • selectv2.test.tsx: add regression tests for non-searchable and searchable Select inside a plain <form>
  • Form.test.tsx: keep the "plain text input still submits" test (Form's own contract); drop the Select-inside-Form test, which now lives next to the component that owns the behaviour

Details: why this solution, alternatives considered, references

Root cause

The browser's implicit submission mechanism submits a <form> when Enter is pressed on a text-like input. React-select renders an internal <input> (readonly for non-searchable, writable for searchable) which the browser treats as a regular text input. React-select does not call preventDefault() on Enter when the menu is closed, so the browser proceeds with implicit submission.

Why fix this in the Select component

Per the WHATWG HTML spec, implicit submission is designed for text fields. The WAI-ARIA Combobox Pattern defines Enter on a combobox as "accept the autocomplete suggestion / pick the highlighted option"; it never delegates Enter to the surrounding form. A combobox is a composite widget that consumes Enter itself.

Putting the fix on the <form> would mean every form container has to know about every composite widget that consumes Enter (Select today, DatePicker / Autocomplete / custom dropdowns tomorrow), and it would only work when the Select is used inside our <Form> component — not when consumers wrap it in their own <form>. The widget owns its keyboard behaviour, so the fix belongs in the widget.

Implementation

A display: contents div wraps react-select. The div sits in the DOM and event-flow but generates no box of its own — no layout impact, no width/margin/flex changes. Its onKeyDown runs on the bubble phase, after react-select's own handler. When react-select consumes Enter for option selection it already calls preventDefault() internally; our handler is redundant in that case (harmless). When react-select does not consume Enter (menu closed, or search field with no highlighted option), our handler calls preventDefault() and the browser skips implicit submission.

Alternatives considered

1. Form-level handler with a text-input whitelist — the first version of this PR. Only blocked Enter on readonly inputs, so a searchable Select (>8 options) still submitted the form. It also did nothing when the Select was used inside a consumer's own <form>. Rejected after review feedback from @JBWatenbergScality flagged the searchable-Select regression.

2. preventDefault() inside react-select's onKeyDown prop — not viable. React-select checks event.defaultPrevented and skips all keyboard handling if set, breaking menu opening and option selection.

3. stopPropagation() inside react-select's onKeyDown prop — not viable. Implicit form submission is a browser default action, not triggered by event bubbling. stopPropagation() cannot prevent default actions.

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Feb 25, 2026

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented Feb 25, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

Comment on lines +275 to +289
const TEXT_INPUT_TYPES = ['text', 'search', 'url', 'tel', 'password', 'email', 'number'];

const preventImplicitSubmission = (
event: KeyboardEvent<HTMLFormElement>,
) => {
if (event.key !== 'Enter') return;
const target = event.target as HTMLElement;
const isTextInput =
target instanceof HTMLInputElement &&
TEXT_INPUT_TYPES.includes(target.type) &&
!target.readOnly;
if (!isTextInput && !(target instanceof HTMLButtonElement)) {
event.preventDefault();
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really unsure about this proposal. What if we have a select with search field (because we have more than 8 items) ? Then pressing enter on the search field trigger the submission.

Are we trying to solve a real problem in this PR ? What is described as a pb in here is a standard behavior I think.

@JeanMarcMilletScality JeanMarcMilletScality marked this pull request as draft February 26, 2026 10:36
@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented May 21, 2026

Branches have diverged

This pull request's source branch bugfix/CUI-5-select-enter-prevents-default has diverged from
development/1.0 by more than 100 commits.

To avoid any integration risks, please re-synchronize them using one of the
following solutions:

  • Merge origin/development/1.0 into bugfix/CUI-5-select-enter-prevents-default
  • Rebase bugfix/CUI-5-select-enter-prevents-default onto origin/development/1.0

Note: If you choose to rebase, you may have to ask me to rebuild
integration branches using the reset command.

JeanMarcMilletScality and others added 2 commits May 21, 2026 17:21
The Form component now intercepts Enter keydown events and only allows
form submission when the focused element is a writable text input or a
button. This prevents custom widgets like Select from accidentally
triggering form submission when users press Enter to interact with them.
Move the fix from the Form to the Select. The Select wraps react-select
in a display: contents div with an onKeyDown that preventDefaults Enter,
so Enter never escapes to trigger the browser's implicit submission —
regardless of whether the inner input is readonly or a writable search
field, and regardless of which form container the Select is in.

The previous Form-level fix only blocked Enter on readonly inputs, so a
searchable Select (>8 options) still submitted the form. It also did
nothing when the Select was used inside a consumer's own <form>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JeanMarcMilletScality JeanMarcMilletScality force-pushed the bugfix/CUI-5-select-enter-prevents-default branch from 3886ecb to fb383df Compare May 21, 2026 15:29
@bert-e
Copy link
Copy Markdown
Contributor

bert-e commented May 21, 2026

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

@JeanMarcMilletScality JeanMarcMilletScality marked this pull request as ready for review May 21, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants