Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b088fd3
UI: Add ControlWithError component
oandregal Aug 13, 2026
ec779a2
Components: Use ControlWithError from @wordpress/ui
oandregal Aug 13, 2026
3b5e248
DataViews: Use ControlWithError from @wordpress/ui
oandregal Aug 13, 2026
8a594da
UI: Export ValidityIndicator from control-with-error
oandregal Aug 14, 2026
9f42f42
DataViews: Use ValidityIndicator from @wordpress/ui in the date control
oandregal Aug 14, 2026
0958668
Fields: Use ValidityIndicator from @wordpress/ui in MediaEdit
oandregal Aug 14, 2026
460fc58
Components: Remove dead validated-control indicator styles
oandregal Aug 14, 2026
f5e3bbe
ESLint plugin: Allow ValidityIndicator from @wordpress/ui
oandregal Aug 14, 2026
133da46
Update links e2e test to assert on visible validation message
oandregal Aug 14, 2026
84dd1f1
UI: Add ValidityIndicator story and tests
oandregal Aug 14, 2026
daaf80c
Link changelog entries to the current PR
oandregal Aug 19, 2026
3156998
Cover ControlWithError in the eslint-plugin changelog entry
oandregal Aug 19, 2026
f857ebb
Document ValidityIndicator and update the components manifest
oandregal Aug 19, 2026
73763c4
ControlWithError: Clear stale validity messages as the user edits the…
oandregal Aug 19, 2026
f19010a
UI: Show an error border on invalid controls with visible validity
oandregal Aug 19, 2026
56edc77
UI: Color the focus ring with the error stroke on invalid controls
oandregal Aug 20, 2026
e86579d
Test string label cloning in ControlWithError directly
mirka Aug 20, 2026
026550c
Use a native input stub in ControlWithError tests
mirka Aug 20, 2026
89a004b
Remove "open-box" validation indicator tests
mirka Aug 20, 2026
0ea8659
Move stories to primitives
mirka Aug 20, 2026
beb7ba9
Mark ControlWithError as use with caution in Storybook
mirka Aug 20, 2026
ec2833d
Move ControlWithError and ValidityIndicator to form primitives
mirka Aug 20, 2026
0b7a111
Simplify InputLayout invalid state selector
mirka Aug 20, 2026
c22cef1
Update components manifest snapshot
mirka Aug 20, 2026
d596cc9
Remove uncontrolled onInput sync from ControlWithError
mirka Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
- Remove `ValidatedToggleControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81492](https://github.com/WordPress/gutenberg/pull/81492)).
- Remove `ValidatedToggleGroupControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81450](https://github.com/WordPress/gutenberg/pull/81450)).
- `ToggleGroupControl`: Streamline focus ring color styles ([#81242](https://github.com/WordPress/gutenberg/pull/81242)).
- `ControlWithError`: Remove the internal implementation in favor of the `ControlWithError` component from `@wordpress/ui` ([#81230](https://github.com/WordPress/gutenberg/issues/81230)) ([#81574](https://github.com/WordPress/gutenberg/pull/81574)).
- Remove the unused `components-validated-control__indicator*` styles; the indicator is now rendered and styled by `ValidityIndicator` from `@wordpress/ui` ([#81230](https://github.com/WordPress/gutenberg/issues/81230)) ([#81574](https://github.com/WordPress/gutenberg/pull/81574)).

## 39.0.0 (2026-08-12)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import { forwardRef, useRef } from '@wordpress/element';
import { ControlWithError } from '../control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';
import ContentEditableControl from '../../content-editable-control';

Expand Down Expand Up @@ -34,6 +34,7 @@ const UnforwardedValidatedContentEditableControl = (
return (
<div className="components-validated-control__wrapper-with-error-delegate">
<ControlWithError
className="components-validated-control"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ControlWithError handles the messaging and also the styles of that message (red/green based on invalid/valid state). However, the consumer needs to also set other styles (e.g., border to red in invalid state) based on the data-validity-visible data attribute, etc.

This is a necessary first step, but it is also too low-level and requires tight coordination with how this works. We need to have the validated controls in UI to make this seamless to consumers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll also look into some ways to make custom composition easier.

required={ required }
markWhenOptional={ markWhenOptional }
customValidity={ customValidity }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { forwardRef, useRef } from '@wordpress/element';
import { ControlWithError } from '../control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';
import CustomSelectControl from '../../custom-select-control';

Expand All @@ -21,6 +21,7 @@ const UnforwardedValidatedCustomSelectControl = (
ref={ forwardedRef }
>
<ControlWithError
className="components-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
customValidity={ customValidity }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef, useRef } from '@wordpress/element';
import { useMergeRefs } from '@wordpress/compose';
import { ControlWithError } from '../control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';
import InputControl from '../../input-control';

Expand All @@ -18,6 +18,7 @@ const UnforwardedValidatedInputControl = (

return (
<ControlWithError
className="components-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
customValidity={ customValidity }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMergeRefs } from '@wordpress/compose';
import { forwardRef, useRef } from '@wordpress/element';
import { ControlWithError } from '../control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';
import RangeControl from '../../range-control';

Expand All @@ -18,6 +18,7 @@ const UnforwardedValidatedRangeControl = (

return (
<ControlWithError
className="components-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
customValidity={ customValidity }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { expect, userEvent, waitFor, within } from 'storybook/test';
import clsx from 'clsx';
import { useRef, useCallback, useState } from '@wordpress/element';
import { debounce } from '@wordpress/compose';
import type { ControlWithError } from '@wordpress/ui';
import { ValidatedInputControl } from '..';
import { formDecorator } from './story-utils';
import type { ControlWithError } from '../../control-with-error';
import Dropdown from '../../../dropdown';
import { Button } from '../../../button';
import Modal from '../../../modal';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef, useRef } from '@wordpress/element';
import { useMergeRefs } from '@wordpress/compose';
import { ControlWithError } from '../control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';
import TextareaControl from '../../textarea-control';

Expand All @@ -18,6 +18,7 @@ const UnforwardedValidatedTextareaControl = (

return (
<ControlWithError
className="components-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
customValidity={ customValidity }
Expand Down
1 change: 0 additions & 1 deletion packages/components/src/validated-form-controls/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './components';
export * from './control-with-error';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mirka wouldn't this cause issues if a consumer uses a DataViews version that depends on the ControlWithError or ValidityIndicator components exposed via the wp.components.privateApis global?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I got the impression that we shouldn't remove these private APIs but rather deprecate them and keep them for 2 major WordPress versions (related).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Actually, scratch that. I just double-checked plus checked history and they had never been exported via the private APIs.

45 changes: 0 additions & 45 deletions packages/components/src/validated-form-controls/style.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
@use "@wordpress/base-styles/colors" as *;
@use "@wordpress/base-styles/variables" as *;
@use "../utils/theme-variables" as *;

%red-focus-ring {
--focus-color: #{ $alert-red };
Expand Down Expand Up @@ -50,46 +48,3 @@
opacity: 0;
pointer-events: none;
}

.components-validated-control__indicator {
display: flex;
align-items: flex-start;
gap: 4px;
margin: 8px 0 0;
font-family: $font-family-body;
font-size: $font-size-small;
line-height: 16px; // matches the icon size
color: $components-color-gray-700;
animation:
components-validated-control__indicator-jump 0.2s
cubic-bezier(0.68, -0.55, 0.27, 1.55);

&.is-invalid {
color: $alert-red;
}

&.is-valid {
color: color-mix(in srgb, $black 30%, $alert-green);
}
}

.components-validated-control__indicator-icon {
flex-shrink: 0;
}

.components-validated-control__indicator-spinner {
margin: 2px;
width: $grid-unit-15;
height: $grid-unit-15;
}

@keyframes components-validated-control__indicator-jump {
0% {
transform: translateY(-4px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}

This file was deleted.

2 changes: 2 additions & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
- DataForm: Internalize `ValidatedToggleControl` instead of unlocking it from the `@wordpress/components` private APIs. [#81492](https://github.com/WordPress/gutenberg/pull/81492)
- DataForm: Internalize `ValidatedToggleGroupControl` instead of unlocking it from the `@wordpress/components` private APIs. [#81450](https://github.com/WordPress/gutenberg/pull/81450)
- `ValidatedToggleGroupControl`: Use `--focus-color` for the error focus ring so ancestor overrides apply correctly ([#81242](https://github.com/WordPress/gutenberg/pull/81242)).
- DataForm: Remove the vendored `ControlWithError` copy in favor of the `ControlWithError` component from `@wordpress/ui`. ([#81230](https://github.com/WordPress/gutenberg/issues/81230)) ([#81574](https://github.com/WordPress/gutenberg/pull/81574))
- DataForm: Render the `date` control's validity message with `ValidityIndicator` from `@wordpress/ui` instead of hand-rolled markup styled by `@wordpress/components` global class names. ([#81230](https://github.com/WordPress/gutenberg/issues/81230)) ([#81574](https://github.com/WordPress/gutenberg/pull/81574))

## 18.0.0 (2026-08-12)

Expand Down
30 changes: 10 additions & 20 deletions packages/dataviews/src/components/dataform-controls/date.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import clsx from 'clsx';
import {
format,
isValid as isValidDate,
Expand All @@ -11,7 +10,6 @@ import {
import {
BaseControl,
Button,
Icon as WCIcon,
__experimentalInputControl as InputControl,
} from '@wordpress/components';
import { speak } from '@wordpress/a11y';
Expand All @@ -24,8 +22,12 @@ import {
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { getDate, getSettings } from '@wordpress/date';
import { error as errorIcon } from '@wordpress/icons';
import { Calendar, RangeCalendar, Stack } from '@wordpress/ui';
import {
Calendar,
RangeCalendar,
Stack,
ValidityIndicator,
} from '@wordpress/ui';
import RelativeDateControl from './utils/relative-date-control';
import useDisabledDateMatchers from './utils/use-disabled-date-matchers';
import {
Expand Down Expand Up @@ -253,22 +255,10 @@ function ValidatedDateControl< Item >( {
<div onBlur={ onBlur }>
{ children }
{ customValidity && (
<p
className={ clsx(
'components-validated-control__indicator',
customValidity.type === 'invalid'
? 'is-invalid'
: undefined
) }
>
<WCIcon
className="components-validated-control__indicator-icon"
icon={ errorIcon }
size={ 16 }
fill="currentColor"
/>
{ customValidity.message }
</p>
<ValidityIndicator
type={ customValidity.type }
message={ customValidity.message }
/>
) }
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef, useRef } from '@wordpress/element';
import { useMergeRefs } from '@wordpress/compose';
import { CheckboxControl } from '@wordpress/components';
import { ControlWithError } from './control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';

type CheckboxControlProps = React.ComponentProps< typeof CheckboxControl >;
Expand All @@ -23,6 +23,7 @@ const UnforwardedValidatedCheckboxControl = (

return (
<ControlWithError
className="dataviews-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
ref={ mergedRefs }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef, useEffect, useRef } from '@wordpress/element';
import { useMergeRefs } from '@wordpress/compose';
import { ComboboxControl } from '@wordpress/components';
import { ControlWithError } from './control-with-error';
import { ControlWithError } from '@wordpress/ui';
import type { ValidatedControlProps } from './types';

type ComboboxControlProps = React.ComponentProps< typeof ComboboxControl >;
Expand Down Expand Up @@ -39,6 +39,7 @@ const UnforwardedValidatedComboboxControl = (
return (
// TODO: Bug - Missing value error is not cleared immediately on change, waits for blur.
<ControlWithError
className="dataviews-validated-control"
required={ required }
markWhenOptional={ markWhenOptional }
ref={ mergedRefs }
Expand Down
Loading
Loading