Skip to content

Conversation

@mirka
Copy link
Member

@mirka mirka commented Nov 25, 2025

What?

Separates the HTML-level validity state from the visible display state of the validity messages.

Why/How?

Our validity states can be conceptualized as having two states:

  1. Canonical validity state as held by the HTML-level browser APIs.
  2. Visibility state of the validity messages as shown to the user.

As per the agreed upon spec, we only want to start showing the validity messages to the user after a field has been first blurred, marking it as "touched".

In our previous implementation, standard constraints (like required) updated HTML-level validity state in real time, while custom validity tried to update HTML-level validity state in sync with the message visibility state. This meant that, at the HTML level, custom validity messages set by the consumer weren't marking the field as invalid until the first blur.

This caused complications when a form is submitted with a field that would be considered invalid by the consumer's custom validity logic, but the HTML does not know that yet because the field has never blurred. We would have to trigger the custom validity logic imperatively on the submit events of the parent form.

This was a bad choice in hindsight, so here we are restructuring the state so all validity state is updated real time at the HTML level. This gives us some benefits:

  • The onValidate prop can be removed entirely, because now the consumer's custom validation logic should be triggered on every change (onChange).
  • The internal logic is much simplified.
  • As long as the consumer has already marked a field as invalid, it will always prevent the form from submitting, regardless of "touched" state.

Punted to follow up

How to delay or prevent form submission when an async validation is still in flight (#71310).

Testing Instructions

Check stories for Validated Form Controls. Behaviors are basically the same, except for some minor improvements in the async validation example.

For code review, a commit-by-commit review should be easier. The bulk of the changed files are 8bfa861, 35d6513, and 6187246, which are just mechanical refactors and not very interesting.

@mirka mirka self-assigned this Nov 25, 2025
@mirka mirka added [Type] Breaking Change For PRs that introduce a change that will break existing functionality [Package] Components /packages/components labels Nov 25, 2025
@github-actions
Copy link

github-actions bot commented Nov 25, 2025

Size Change: -151 B (-0.01%)

Total Size: 2.57 MB

Filename Size Change
build/scripts/components/index.min.js 272 kB -174 B (-0.06%)
build/styles/components/style-rtl.css 14 kB +12 B (+0.09%)
build/styles/components/style.css 14 kB +11 B (+0.08%)
ℹ️ View Unchanged
Filename Size
build/modules/a11y/index.min.js 355 B
build/modules/abilities/index.min.js 42.3 kB
build/modules/block-editor/utils/fit-text-frontend.min.js 549 B
build/modules/block-library/accordion/view.min.js 779 B
build/modules/block-library/file/view.min.js 346 B
build/modules/block-library/form/view.min.js 528 B
build/modules/block-library/image/view.min.js 1.95 kB
build/modules/block-library/navigation/view.min.js 1.03 kB
build/modules/block-library/query/view.min.js 518 B
build/modules/block-library/search/view.min.js 498 B
build/modules/block-library/tabs/view.min.js 859 B
build/modules/boot/index.min.js 102 kB
build/modules/core-abilities/index.min.js 890 B
build/modules/edit-site-init/index.min.js 1.41 kB
build/modules/interactivity-router/full-page.min.js 451 B
build/modules/interactivity-router/index.min.js 11.5 kB
build/modules/interactivity/index.min.js 14.9 kB
build/modules/latex-to-mathml/index.min.js 56.5 kB
build/modules/latex-to-mathml/loader.min.js 131 B
build/modules/lazy-editor/index.min.js 18.8 kB
build/modules/route/index.min.js 24.6 kB
build/modules/workflow/index.min.js 36.8 kB
build/scripts/a11y/index.min.js 1.06 kB
build/scripts/annotations/index.min.js 2.38 kB
build/scripts/api-fetch/index.min.js 2.83 kB
build/scripts/autop/index.min.js 2.18 kB
build/scripts/blob/index.min.js 631 B
build/scripts/block-directory/index.min.js 8.03 kB
build/scripts/block-editor/index.min.js 314 kB
build/scripts/block-library/index.min.js 277 kB
build/scripts/block-serialization-default-parser/index.min.js 1.16 kB
build/scripts/block-serialization-spec-parser/index.min.js 3.08 kB
build/scripts/blocks/index.min.js 56.4 kB
build/scripts/commands/index.min.js 19.9 kB
build/scripts/compose/index.min.js 13.9 kB
build/scripts/core-commands/index.min.js 4.13 kB
build/scripts/core-data/index.min.js 86.7 kB
build/scripts/customize-widgets/index.min.js 12.3 kB
build/scripts/data-controls/index.min.js 793 B
build/scripts/data/index.min.js 9.62 kB
build/scripts/date/index.min.js 23.6 kB
build/scripts/deprecated/index.min.js 752 B
build/scripts/dom-ready/index.min.js 476 B
build/scripts/dom/index.min.js 4.91 kB
build/scripts/edit-post/index.min.js 16.3 kB
build/scripts/edit-site/index.min.js 234 kB
build/scripts/edit-widgets/index.min.js 20 kB
build/scripts/editor/index.min.js 284 kB
build/scripts/element/index.min.js 5.19 kB
build/scripts/escape-html/index.min.js 586 B
build/scripts/format-library/index.min.js 10.8 kB
build/scripts/hooks/index.min.js 1.83 kB
build/scripts/html-entities/index.min.js 494 B
build/scripts/i18n/index.min.js 2.46 kB
build/scripts/is-shallow-equal/index.min.js 568 B
build/scripts/keyboard-shortcuts/index.min.js 1.57 kB
build/scripts/keycodes/index.min.js 1.53 kB
build/scripts/list-reusable-blocks/index.min.js 2.44 kB
build/scripts/media-utils/index.min.js 69.5 kB
build/scripts/notices/index.min.js 1.11 kB
build/scripts/nux/index.min.js 1.88 kB
build/scripts/patterns/index.min.js 7.88 kB
build/scripts/plugins/index.min.js 2.14 kB
build/scripts/preferences-persistence/index.min.js 2.15 kB
build/scripts/preferences/index.min.js 3.31 kB
build/scripts/primitives/index.min.js 1.01 kB
build/scripts/priority-queue/index.min.js 1.61 kB
build/scripts/private-apis/index.min.js 1.06 kB
build/scripts/react-i18n/index.min.js 832 B
build/scripts/react-refresh-entry/index.min.js 9.44 kB
build/scripts/react-refresh-runtime/index.min.js 3.59 kB
build/scripts/redux-routine/index.min.js 3.36 kB
build/scripts/reusable-blocks/index.min.js 2.93 kB
build/scripts/rich-text/index.min.js 12.9 kB
build/scripts/router/index.min.js 5.96 kB
build/scripts/server-side-render/index.min.js 1.91 kB
build/scripts/shortcode/index.min.js 1.58 kB
build/scripts/style-engine/index.min.js 2.33 kB
build/scripts/theme/index.min.js 20.8 kB
build/scripts/token-list/index.min.js 739 B
build/scripts/undo-manager/index.min.js 917 B
build/scripts/url/index.min.js 3.98 kB
build/scripts/vendors/react-dom.min.js 43 kB
build/scripts/vendors/react-jsx-runtime.min.js 691 B
build/scripts/vendors/react.min.js 4.27 kB
build/scripts/viewport/index.min.js 1.22 kB
build/scripts/warning/index.min.js 454 B
build/scripts/widgets/index.min.js 7.81 kB
build/scripts/wordcount/index.min.js 1.04 kB
build/styles/block-directory/style-rtl.css 1.05 kB
build/styles/block-directory/style.css 1.05 kB
build/styles/block-editor/content-rtl.css 4.8 kB
build/styles/block-editor/content.css 4.79 kB
build/styles/block-editor/default-editor-styles-rtl.css 224 B
build/styles/block-editor/default-editor-styles.css 224 B
build/styles/block-editor/style-rtl.css 16.4 kB
build/styles/block-editor/style.css 16.4 kB
build/styles/block-library/accordion-heading/style-rtl.css 325 B
build/styles/block-library/accordion-heading/style.css 325 B
build/styles/block-library/accordion-item/style-rtl.css 180 B
build/styles/block-library/accordion-item/style.css 180 B
build/styles/block-library/accordion-panel/style-rtl.css 99 B
build/styles/block-library/accordion-panel/style.css 99 B
build/styles/block-library/accordion/style-rtl.css 62 B
build/styles/block-library/accordion/style.css 62 B
build/styles/block-library/archives/editor-rtl.css 61 B
build/styles/block-library/archives/editor.css 61 B
build/styles/block-library/archives/style-rtl.css 90 B
build/styles/block-library/archives/style.css 90 B
build/styles/block-library/audio/editor-rtl.css 149 B
build/styles/block-library/audio/editor.css 151 B
build/styles/block-library/audio/style-rtl.css 132 B
build/styles/block-library/audio/style.css 132 B
build/styles/block-library/audio/theme-rtl.css 134 B
build/styles/block-library/audio/theme.css 134 B
build/styles/block-library/avatar/editor-rtl.css 115 B
build/styles/block-library/avatar/editor.css 115 B
build/styles/block-library/avatar/style-rtl.css 104 B
build/styles/block-library/avatar/style.css 104 B
build/styles/block-library/breadcrumbs/style-rtl.css 203 B
build/styles/block-library/breadcrumbs/style.css 203 B
build/styles/block-library/button/editor-rtl.css 265 B
build/styles/block-library/button/editor.css 265 B
build/styles/block-library/button/style-rtl.css 554 B
build/styles/block-library/button/style.css 554 B
build/styles/block-library/buttons/editor-rtl.css 291 B
build/styles/block-library/buttons/editor.css 291 B
build/styles/block-library/buttons/style-rtl.css 349 B
build/styles/block-library/buttons/style.css 349 B
build/styles/block-library/calendar/style-rtl.css 239 B
build/styles/block-library/calendar/style.css 239 B
build/styles/block-library/categories/editor-rtl.css 132 B
build/styles/block-library/categories/editor.css 131 B
build/styles/block-library/categories/style-rtl.css 152 B
build/styles/block-library/categories/style.css 152 B
build/styles/block-library/classic-rtl.css 321 B
build/styles/block-library/classic.css 321 B
build/styles/block-library/code/editor-rtl.css 53 B
build/styles/block-library/code/editor.css 53 B
build/styles/block-library/code/style-rtl.css 139 B
build/styles/block-library/code/style.css 139 B
build/styles/block-library/code/theme-rtl.css 122 B
build/styles/block-library/code/theme.css 122 B
build/styles/block-library/columns/editor-rtl.css 108 B
build/styles/block-library/columns/editor.css 108 B
build/styles/block-library/columns/style-rtl.css 421 B
build/styles/block-library/columns/style.css 421 B
build/styles/block-library/comment-author-avatar/editor-rtl.css 124 B
build/styles/block-library/comment-author-avatar/editor.css 124 B
build/styles/block-library/comment-author-name/style-rtl.css 72 B
build/styles/block-library/comment-author-name/style.css 72 B
build/styles/block-library/comment-content/style-rtl.css 120 B
build/styles/block-library/comment-content/style.css 120 B
build/styles/block-library/comment-date/style-rtl.css 65 B
build/styles/block-library/comment-date/style.css 65 B
build/styles/block-library/comment-edit-link/style-rtl.css 70 B
build/styles/block-library/comment-edit-link/style.css 70 B
build/styles/block-library/comment-reply-link/style-rtl.css 71 B
build/styles/block-library/comment-reply-link/style.css 71 B
build/styles/block-library/comment-template/style-rtl.css 191 B
build/styles/block-library/comment-template/style.css 191 B
build/styles/block-library/comments-pagination-numbers/editor-rtl.css 122 B
build/styles/block-library/comments-pagination-numbers/editor.css 121 B
build/styles/block-library/comments-pagination/editor-rtl.css 168 B
build/styles/block-library/comments-pagination/editor.css 168 B
build/styles/block-library/comments-pagination/style-rtl.css 201 B
build/styles/block-library/comments-pagination/style.css 201 B
build/styles/block-library/comments-title/editor-rtl.css 75 B
build/styles/block-library/comments-title/editor.css 75 B
build/styles/block-library/comments/editor-rtl.css 842 B
build/styles/block-library/comments/editor.css 842 B
build/styles/block-library/comments/style-rtl.css 637 B
build/styles/block-library/comments/style.css 637 B
build/styles/block-library/common-rtl.css 1.11 kB
build/styles/block-library/common.css 1.11 kB
build/styles/block-library/cover/editor-rtl.css 631 B
build/styles/block-library/cover/editor.css 631 B
build/styles/block-library/cover/style-rtl.css 1.82 kB
build/styles/block-library/cover/style.css 1.81 kB
build/styles/block-library/details/editor-rtl.css 65 B
build/styles/block-library/details/editor.css 65 B
build/styles/block-library/details/style-rtl.css 86 B
build/styles/block-library/details/style.css 86 B
build/styles/block-library/editor-elements-rtl.css 75 B
build/styles/block-library/editor-elements.css 75 B
build/styles/block-library/editor-rtl.css 11.8 kB
build/styles/block-library/editor.css 11.8 kB
build/styles/block-library/elements-rtl.css 54 B
build/styles/block-library/elements.css 54 B
build/styles/block-library/embed/editor-rtl.css 331 B
build/styles/block-library/embed/editor.css 331 B
build/styles/block-library/embed/style-rtl.css 448 B
build/styles/block-library/embed/style.css 448 B
build/styles/block-library/embed/theme-rtl.css 133 B
build/styles/block-library/embed/theme.css 133 B
build/styles/block-library/file/editor-rtl.css 324 B
build/styles/block-library/file/editor.css 324 B
build/styles/block-library/file/style-rtl.css 278 B
build/styles/block-library/file/style.css 278 B
build/styles/block-library/footnotes/style-rtl.css 198 B
build/styles/block-library/footnotes/style.css 197 B
build/styles/block-library/form-input/editor-rtl.css 229 B
build/styles/block-library/form-input/editor.css 229 B
build/styles/block-library/form-input/style-rtl.css 366 B
build/styles/block-library/form-input/style.css 366 B
build/styles/block-library/form-submission-notification/editor-rtl.css 344 B
build/styles/block-library/form-submission-notification/editor.css 341 B
build/styles/block-library/form-submit-button/style-rtl.css 69 B
build/styles/block-library/form-submit-button/style.css 69 B
build/styles/block-library/freeform/editor-rtl.css 2.59 kB
build/styles/block-library/freeform/editor.css 2.59 kB
build/styles/block-library/gallery/editor-rtl.css 615 B
build/styles/block-library/gallery/editor.css 616 B
build/styles/block-library/gallery/style-rtl.css 1.84 kB
build/styles/block-library/gallery/style.css 1.84 kB
build/styles/block-library/gallery/theme-rtl.css 108 B
build/styles/block-library/gallery/theme.css 108 B
build/styles/block-library/group/editor-rtl.css 335 B
build/styles/block-library/group/editor.css 335 B
build/styles/block-library/group/style-rtl.css 103 B
build/styles/block-library/group/style.css 103 B
build/styles/block-library/group/theme-rtl.css 79 B
build/styles/block-library/group/theme.css 79 B
build/styles/block-library/heading/style-rtl.css 205 B
build/styles/block-library/heading/style.css 205 B
build/styles/block-library/html/editor-rtl.css 419 B
build/styles/block-library/html/editor.css 419 B
build/styles/block-library/image/editor-rtl.css 763 B
build/styles/block-library/image/editor.css 763 B
build/styles/block-library/image/style-rtl.css 1.6 kB
build/styles/block-library/image/style.css 1.59 kB
build/styles/block-library/image/theme-rtl.css 137 B
build/styles/block-library/image/theme.css 137 B
build/styles/block-library/latest-comments/style-rtl.css 355 B
build/styles/block-library/latest-comments/style.css 354 B
build/styles/block-library/latest-posts/editor-rtl.css 139 B
build/styles/block-library/latest-posts/editor.css 138 B
build/styles/block-library/latest-posts/style-rtl.css 520 B
build/styles/block-library/latest-posts/style.css 520 B
build/styles/block-library/list/style-rtl.css 107 B
build/styles/block-library/list/style.css 107 B
build/styles/block-library/loginout/style-rtl.css 61 B
build/styles/block-library/loginout/style.css 61 B
build/styles/block-library/math/editor-rtl.css 105 B
build/styles/block-library/math/editor.css 105 B
build/styles/block-library/math/style-rtl.css 61 B
build/styles/block-library/math/style.css 61 B
build/styles/block-library/media-text/editor-rtl.css 321 B
build/styles/block-library/media-text/editor.css 320 B
build/styles/block-library/media-text/style-rtl.css 543 B
build/styles/block-library/media-text/style.css 542 B
build/styles/block-library/more/editor-rtl.css 393 B
build/styles/block-library/more/editor.css 393 B
build/styles/block-library/navigation-link/editor-rtl.css 645 B
build/styles/block-library/navigation-link/editor.css 647 B
build/styles/block-library/navigation-link/style-rtl.css 190 B
build/styles/block-library/navigation-link/style.css 188 B
build/styles/block-library/navigation-submenu/editor-rtl.css 295 B
build/styles/block-library/navigation-submenu/editor.css 294 B
build/styles/block-library/navigation/editor-rtl.css 2.24 kB
build/styles/block-library/navigation/editor.css 2.24 kB
build/styles/block-library/navigation/style-rtl.css 2.27 kB
build/styles/block-library/navigation/style.css 2.25 kB
build/styles/block-library/nextpage/editor-rtl.css 392 B
build/styles/block-library/nextpage/editor.css 392 B
build/styles/block-library/page-list/editor-rtl.css 356 B
build/styles/block-library/page-list/editor.css 356 B
build/styles/block-library/page-list/style-rtl.css 192 B
build/styles/block-library/page-list/style.css 192 B
build/styles/block-library/paragraph/editor-rtl.css 251 B
build/styles/block-library/paragraph/editor.css 251 B
build/styles/block-library/paragraph/style-rtl.css 341 B
build/styles/block-library/paragraph/style.css 340 B
build/styles/block-library/post-author-biography/style-rtl.css 74 B
build/styles/block-library/post-author-biography/style.css 74 B
build/styles/block-library/post-author-name/style-rtl.css 69 B
build/styles/block-library/post-author-name/style.css 69 B
build/styles/block-library/post-author/style-rtl.css 188 B
build/styles/block-library/post-author/style.css 189 B
build/styles/block-library/post-comments-count/style-rtl.css 72 B
build/styles/block-library/post-comments-count/style.css 72 B
build/styles/block-library/post-comments-form/editor-rtl.css 96 B
build/styles/block-library/post-comments-form/editor.css 96 B
build/styles/block-library/post-comments-form/style-rtl.css 525 B
build/styles/block-library/post-comments-form/style.css 525 B
build/styles/block-library/post-comments-link/style-rtl.css 71 B
build/styles/block-library/post-comments-link/style.css 71 B
build/styles/block-library/post-content/style-rtl.css 61 B
build/styles/block-library/post-content/style.css 61 B
build/styles/block-library/post-date/style-rtl.css 62 B
build/styles/block-library/post-date/style.css 62 B
build/styles/block-library/post-excerpt/editor-rtl.css 71 B
build/styles/block-library/post-excerpt/editor.css 71 B
build/styles/block-library/post-excerpt/style-rtl.css 155 B
build/styles/block-library/post-excerpt/style.css 155 B
build/styles/block-library/post-featured-image/editor-rtl.css 719 B
build/styles/block-library/post-featured-image/editor.css 717 B
build/styles/block-library/post-featured-image/style-rtl.css 347 B
build/styles/block-library/post-featured-image/style.css 347 B
build/styles/block-library/post-navigation-link/style-rtl.css 215 B
build/styles/block-library/post-navigation-link/style.css 214 B
build/styles/block-library/post-template/style-rtl.css 414 B
build/styles/block-library/post-template/style.css 414 B
build/styles/block-library/post-terms/style-rtl.css 96 B
build/styles/block-library/post-terms/style.css 96 B
build/styles/block-library/post-time-to-read/style-rtl.css 70 B
build/styles/block-library/post-time-to-read/style.css 70 B
build/styles/block-library/post-title/style-rtl.css 162 B
build/styles/block-library/post-title/style.css 162 B
build/styles/block-library/preformatted/style-rtl.css 125 B
build/styles/block-library/preformatted/style.css 125 B
build/styles/block-library/pullquote/editor-rtl.css 133 B
build/styles/block-library/pullquote/editor.css 133 B
build/styles/block-library/pullquote/style-rtl.css 365 B
build/styles/block-library/pullquote/style.css 365 B
build/styles/block-library/pullquote/theme-rtl.css 176 B
build/styles/block-library/pullquote/theme.css 176 B
build/styles/block-library/query-pagination-numbers/editor-rtl.css 121 B
build/styles/block-library/query-pagination-numbers/editor.css 118 B
build/styles/block-library/query-pagination/editor-rtl.css 154 B
build/styles/block-library/query-pagination/editor.css 154 B
build/styles/block-library/query-pagination/style-rtl.css 237 B
build/styles/block-library/query-pagination/style.css 237 B
build/styles/block-library/query-title/style-rtl.css 64 B
build/styles/block-library/query-title/style.css 64 B
build/styles/block-library/query-total/style-rtl.css 64 B
build/styles/block-library/query-total/style.css 64 B
build/styles/block-library/query/editor-rtl.css 438 B
build/styles/block-library/query/editor.css 438 B
build/styles/block-library/quote/style-rtl.css 238 B
build/styles/block-library/quote/style.css 238 B
build/styles/block-library/quote/theme-rtl.css 233 B
build/styles/block-library/quote/theme.css 236 B
build/styles/block-library/read-more/style-rtl.css 131 B
build/styles/block-library/read-more/style.css 131 B
build/styles/block-library/reset-rtl.css 472 B
build/styles/block-library/reset.css 472 B
build/styles/block-library/rss/editor-rtl.css 126 B
build/styles/block-library/rss/editor.css 126 B
build/styles/block-library/rss/style-rtl.css 284 B
build/styles/block-library/rss/style.css 283 B
build/styles/block-library/search/editor-rtl.css 199 B
build/styles/block-library/search/editor.css 199 B
build/styles/block-library/search/style-rtl.css 665 B
build/styles/block-library/search/style.css 666 B
build/styles/block-library/search/theme-rtl.css 113 B
build/styles/block-library/search/theme.css 113 B
build/styles/block-library/separator/editor-rtl.css 100 B
build/styles/block-library/separator/editor.css 100 B
build/styles/block-library/separator/style-rtl.css 248 B
build/styles/block-library/separator/style.css 248 B
build/styles/block-library/separator/theme-rtl.css 195 B
build/styles/block-library/separator/theme.css 195 B
build/styles/block-library/shortcode/editor-rtl.css 286 B
build/styles/block-library/shortcode/editor.css 286 B
build/styles/block-library/site-logo/editor-rtl.css 773 B
build/styles/block-library/site-logo/editor.css 770 B
build/styles/block-library/site-logo/style-rtl.css 218 B
build/styles/block-library/site-logo/style.css 218 B
build/styles/block-library/site-tagline/editor-rtl.css 87 B
build/styles/block-library/site-tagline/editor.css 87 B
build/styles/block-library/site-tagline/style-rtl.css 65 B
build/styles/block-library/site-tagline/style.css 65 B
build/styles/block-library/site-title/editor-rtl.css 85 B
build/styles/block-library/site-title/editor.css 85 B
build/styles/block-library/site-title/style-rtl.css 143 B
build/styles/block-library/site-title/style.css 143 B
build/styles/block-library/social-link/editor-rtl.css 314 B
build/styles/block-library/social-link/editor.css 314 B
build/styles/block-library/social-links/editor-rtl.css 339 B
build/styles/block-library/social-links/editor.css 338 B
build/styles/block-library/social-links/style-rtl.css 1.51 kB
build/styles/block-library/social-links/style.css 1.51 kB
build/styles/block-library/spacer/editor-rtl.css 346 B
build/styles/block-library/spacer/editor.css 346 B
build/styles/block-library/spacer/style-rtl.css 48 B
build/styles/block-library/spacer/style.css 48 B
build/styles/block-library/style-rtl.css 16.5 kB
build/styles/block-library/style.css 16.5 kB
build/styles/block-library/tab/style-rtl.css 202 B
build/styles/block-library/tab/style.css 202 B
build/styles/block-library/table-of-contents/style-rtl.css 83 B
build/styles/block-library/table-of-contents/style.css 83 B
build/styles/block-library/table/editor-rtl.css 394 B
build/styles/block-library/table/editor.css 394 B
build/styles/block-library/table/style-rtl.css 641 B
build/styles/block-library/table/style.css 640 B
build/styles/block-library/table/theme-rtl.css 152 B
build/styles/block-library/table/theme.css 152 B
build/styles/block-library/tabs/editor-rtl.css 236 B
build/styles/block-library/tabs/editor.css 236 B
build/styles/block-library/tabs/style-rtl.css 983 B
build/styles/block-library/tabs/style.css 983 B
build/styles/block-library/tag-cloud/editor-rtl.css 92 B
build/styles/block-library/tag-cloud/editor.css 92 B
build/styles/block-library/tag-cloud/style-rtl.css 248 B
build/styles/block-library/tag-cloud/style.css 248 B
build/styles/block-library/template-part/editor-rtl.css 368 B
build/styles/block-library/template-part/editor.css 368 B
build/styles/block-library/template-part/theme-rtl.css 113 B
build/styles/block-library/template-part/theme.css 113 B
build/styles/block-library/term-count/style-rtl.css 63 B
build/styles/block-library/term-count/style.css 63 B
build/styles/block-library/term-description/style-rtl.css 126 B
build/styles/block-library/term-description/style.css 126 B
build/styles/block-library/term-name/style-rtl.css 62 B
build/styles/block-library/term-name/style.css 62 B
build/styles/block-library/term-template/editor-rtl.css 225 B
build/styles/block-library/term-template/editor.css 225 B
build/styles/block-library/term-template/style-rtl.css 114 B
build/styles/block-library/term-template/style.css 114 B
build/styles/block-library/text-columns/editor-rtl.css 95 B
build/styles/block-library/text-columns/editor.css 95 B
build/styles/block-library/text-columns/style-rtl.css 165 B
build/styles/block-library/text-columns/style.css 165 B
build/styles/block-library/theme-rtl.css 715 B
build/styles/block-library/theme.css 719 B
build/styles/block-library/verse/style-rtl.css 123 B
build/styles/block-library/verse/style.css 123 B
build/styles/block-library/video/editor-rtl.css 415 B
build/styles/block-library/video/editor.css 416 B
build/styles/block-library/video/style-rtl.css 202 B
build/styles/block-library/video/style.css 202 B
build/styles/block-library/video/theme-rtl.css 134 B
build/styles/block-library/video/theme.css 134 B
build/styles/commands/style-rtl.css 1.72 kB
build/styles/commands/style.css 1.72 kB
build/styles/customize-widgets/style-rtl.css 1.44 kB
build/styles/customize-widgets/style.css 1.44 kB
build/styles/edit-post/classic-rtl.css 426 B
build/styles/edit-post/classic.css 427 B
build/styles/edit-post/style-rtl.css 3.38 kB
build/styles/edit-post/style.css 3.38 kB
build/styles/edit-site/style-rtl.css 15.9 kB
build/styles/edit-site/style.css 16 kB
build/styles/edit-widgets/style-rtl.css 4.62 kB
build/styles/edit-widgets/style.css 4.62 kB
build/styles/editor/style-rtl.css 18.6 kB
build/styles/editor/style.css 18.6 kB
build/styles/format-library/style-rtl.css 326 B
build/styles/format-library/style.css 326 B
build/styles/list-reusable-blocks/style-rtl.css 1.02 kB
build/styles/list-reusable-blocks/style.css 1.02 kB
build/styles/media-utils/style-rtl.css 400 B
build/styles/media-utils/style.css 400 B
build/styles/nux/style-rtl.css 622 B
build/styles/nux/style.css 618 B
build/styles/patterns/style-rtl.css 611 B
build/styles/patterns/style.css 611 B
build/styles/preferences/style-rtl.css 415 B
build/styles/preferences/style.css 415 B
build/styles/reusable-blocks/style-rtl.css 275 B
build/styles/reusable-blocks/style.css 275 B
build/styles/widgets/style-rtl.css 1.17 kB
build/styles/widgets/style.css 1.18 kB

compressed-size-action

### Delegate elements

The implementations for `ToggleGroupControl` and `CustomSelectControl` use a "delegate" element for validation, due to upstream limitations that prevent us from using the actual underlying elements for constraint validation. A delegate element in this context is a visually hidden form element that we "delegate" the Constraint Validation API concerns to.
The implementations for `ToggleGroupControl`, `CustomSelectControl`, and `FormTokenField` use a "delegate" element for validation, due to upstream limitations that prevent us from using the actual underlying elements for constraint validation. A delegate element in this context is a visually hidden form element that we "delegate" the Constraint Validation API concerns to.
Copy link
Member Author

Choose a reason for hiding this comment

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

Forgot to add FormTokenField when that was added to the collection.

Comment on lines +112 to +115
<style>
{ `
.my-control:has(:invalid[data-validity-visible]) .my-control__help:not(.is-visible) {
display: none;
Copy link
Member Author

Choose a reason for hiding this comment

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

This "Help text replacement" story is one case where the devex becomes noticeably worse. And I don't think we can have a nice generic API for it, because the conditions for which a consumer may want to replace the help text can be varied.

This pattern was added by designer requests, but I'm still not sure about it's scalability, given how complex the considerations can get in certain cases.

Copy link
Member

Choose a reason for hiding this comment

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

This pattern was added by designer requests, but I'm still not sure about it's scalability, given how complex the considerations can get in certain cases.

Yeah this was my first thought: Whether this is something we should even want to be encouraging. It assumes redundancy between help text and the error message, where ideally they'd be distinct enough to not need this kind of substitution. Maybe I'd need to see some more real-world examples, but I don't think the "duplication" is a terrible default experience that we should focus too much on improving the developer experience around this.

>( undefined );

const timeoutRef = useRef< ReturnType< typeof setTimeout > >();
const previousValidationValueRef = useRef< unknown >( '' );
Copy link
Member Author

Choose a reason for hiding this comment

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

I think I was able to make tweaks in the Async story to make the interactions feel a lot more intuitive and snappier.

}
return label;
}
const VALIDITY_VISIBLE_ATTRIBUTE = 'data-validity-visible';
Copy link
Member Author

Choose a reason for hiding this comment

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

I had trouble naming this. It's not "touched", because messages can be visible without the field being touched. It's also more like "visibility is activated", rather than "a message is visible right now".

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems reasonable to me.

Copy link
Member

Choose a reason for hiding this comment

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

This seems okay to me as well. If seeking an alternative, we could adapt language around "report"ing, for alignment to browser native APIs like reportValidity.

>();
const [ showMessage, setShowMessage ] = useState( false );
const [ isTouched, setIsTouched ] = useState( false );
const previousCustomValidityType = usePrevious( customValidity?.type );
Copy link
Member Author

Choose a reason for hiding this comment

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

This wasn't necessary. It was just masking some unideal state management in the async story.

Comment on lines -201 to -209
const onChange = ( ...args: unknown[] ) => {
children.props.onChange?.( ...args );

// Only validate incrementally if the field has blurred at least once,
// or currently has an error message.
if ( isTouched || errorMessage ) {
onValidate?.();
const message = () => {
if ( errorMessage ) {
return (
<ValidityIndicator type="invalid" message={ errorMessage } />
);
}
};
Copy link
Member Author

Choose a reason for hiding this comment

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

No longer necessary. Now we just pass through the onChange.

Comment on lines -211 to 217
const onKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
// Ensures that custom validators are triggered when the user submits by pressing Enter,
// without ever blurring the control.
if ( event.key === 'Enter' ) {
onValidate?.();
if ( statusMessage?.type ) {
return (
<ValidityIndicator
type={ statusMessage.type }
message={ statusMessage.message }
/>
);
}
return null;
};
Copy link
Member Author

Choose a reason for hiding this comment

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

No longer necessary to handle this case explicitly.

input:invalid[data-validity-visible]
):not(:has([aria-expanded="true"])) {
border-color: $alert-red;
--wp-components-color-accent: #{$alert-red};
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed a small visual bug here. The previous version wasn't correctly overriding a box-shadow color.

.components-validated-control {
// For components based on InputBase
&:has(:is(input, select):user-invalid)
&:has(:is(input, select):invalid[data-validity-visible])
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed these selectors from :user-invalid (won't match our "touched" logic exactly) to :invalid plus an explicit data attribute that we add based on our own logic of when to start showing the error messages.

Copy link
Member

Choose a reason for hiding this comment

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

Changed these selectors from :user-invalid (won't match our "touched" logic exactly) to :invalid plus an explicit data attribute that we add based on our own logic of when to start showing the error messages.

I think I'd missed this part of the current implementation until now. What's different between our logic and the logic of :user-invalid ? I quite like the idea if there's any way we could tap into or align with this native browser behavior. It seems pretty close to what we've implemented?

Copy link
Member Author

Choose a reason for hiding this comment

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

The big reason we can't use :user-invalid is that we sometimes need to show the error even when the user has never interacted with the field.

A more subtle reason is that, in certain compound components, it can be more intuitive for the field to be considered touched only when the focus has left the "component boundary" completely, rather than simply leaving the input element. An example is a password field component with a show/hide button in the suffix. If the user only blurred the input element to toggle the show/hide button and resume editing the input, then they haven't left the component boundary and they're still in the process of editing the password field. This kind of "component atomicity" cannot be captured by a :user-invalid.

@github-actions
Copy link

Flaky tests detected in c08c23b.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/19685140641
📝 Reported issues:

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

Thank you for working on this.

Landing this would help unblock the problem I'm experiencing right now in #73486 whereby me triggering a custom validation does not show up until after the field is blurred.

I'm glad this means we remove onValidate because as someone trying to implement a validated form, it was quite confusing to understand why this was necessary.

}
return label;
}
const VALIDITY_VISIBLE_ATTRIBUTE = 'data-validity-visible';
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems reasonable to me.

@mirka mirka force-pushed the streamline-validation branch from 26e3a19 to cc54e12 Compare December 3, 2025 20:43
@mirka mirka force-pushed the streamline-validation branch from cc54e12 to 98c2c4e Compare December 3, 2025 20:44
@mirka mirka marked this pull request as ready for review December 3, 2025 21:08
@mirka mirka requested a review from ajitbohra as a code owner December 3, 2025 21:08
@github-actions
Copy link

github-actions bot commented Dec 3, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: mirka <[email protected]>
Co-authored-by: getdave <[email protected]>
Co-authored-by: aduth <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@mirka mirka requested a review from a team December 3, 2025 21:08
@mirka
Copy link
Member Author

mirka commented Dec 3, 2025

@oandregal I don't expect these changes to affect internal DataViews implementations, but heads up anyway.

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

This feels a lot simpler and more predictable than the callback approach 👍

Comment on lines 40 to 41
// TODO: Upstream limitation - When form is submitted when value is undefined, it will
// automatically set a clamped value (as defined by `min` attribute, so 0 by default).
Copy link
Member

Choose a reason for hiding this comment

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

Is this comment still relevant?

Suggested change
// TODO: Upstream limitation - When form is submitted when value is undefined, it will
// automatically set a clamped value (as defined by `min` attribute, so 0 by default).

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting, not sure what changed. Maybe #34566. (3edd884)

}
validityTarget?.addEventListener( 'invalid', handler );
return () => validityTarget?.removeEventListener( 'invalid', handler );
} );
Copy link
Member

Choose a reason for hiding this comment

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

Not technically introduced here, but should we be defining dependencies for this effect so that we're not binding / unbinding on every render? Maybe validityTarget should be assigned in the main render path and that becomes the dependency (along with state setter)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Shouldn't matter that much because I don't expect people to be memoizing their getValidityTarget functions, but let's add the dep so readers don't have to wonder about it. (3714604)

Comment on lines +112 to +115
<style>
{ `
.my-control:has(:invalid[data-validity-visible]) .my-control__help:not(.is-visible) {
display: none;
Copy link
Member

Choose a reason for hiding this comment

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

This pattern was added by designer requests, but I'm still not sure about it's scalability, given how complex the considerations can get in certain cases.

Yeah this was my first thought: Whether this is something we should even want to be encouraging. It assumes redundancy between help text and the error message, where ideally they'd be distinct enough to not need this kind of substitution. Maybe I'd need to see some more real-world examples, but I don't think the "duplication" is a terrible default experience that we should focus too much on improving the developer experience around this.

}
return label;
}
const VALIDITY_VISIBLE_ATTRIBUTE = 'data-validity-visible';
Copy link
Member

Choose a reason for hiding this comment

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

This seems okay to me as well. If seeking an alternative, we could adapt language around "report"ing, for alignment to browser native APIs like reportValidity.

.components-validated-control {
// For components based on InputBase
&:has(:is(input, select):user-invalid)
&:has(:is(input, select):invalid[data-validity-visible])
Copy link
Member

Choose a reason for hiding this comment

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

Changed these selectors from :user-invalid (won't match our "touched" logic exactly) to :invalid plus an explicit data attribute that we add based on our own logic of when to start showing the error messages.

I think I'd missed this part of the current implementation until now. What's different between our logic and the logic of :user-invalid ? I quite like the idea if there's any way we could tap into or align with this native browser behavior. It seems pretty close to what we've implemented?

@mirka mirka enabled auto-merge (squash) December 8, 2025 15:34
@mirka mirka merged commit 24c646a into trunk Dec 8, 2025
38 of 39 checks passed
@mirka mirka deleted the streamline-validation branch December 8, 2025 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Components /packages/components [Type] Breaking Change For PRs that introduce a change that will break existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants