Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3278,7 +3278,7 @@ describe( 'URL validation', () => {
] )(
'should prevent submission for $description',
async ( { inputUrl } ) => {
render(
const { container } = render(
<LinkControl
value={ { url: '' } }
forceIsEditingLink
Expand All @@ -3295,7 +3295,7 @@ describe( 'URL validation', () => {
// Wait for validation error to appear
await waitFor( () => {
expect(
screen.getByText( 'Please enter a valid URL.' )
within( container ).getByText( 'Please enter a valid URL.' )
).toBeInTheDocument();
} );

Expand Down Expand Up @@ -3407,7 +3407,7 @@ describe( 'URL validation', () => {
it( 'should show validation error when clicking Apply button with invalid URL', async () => {
// When editing an existing link, use Apply button
const existingLink = { url: 'https://example.com', title: 'Example' };
render(
const { container } = render(
<LinkControl
value={ existingLink }
forceIsEditingLink
Expand All @@ -3428,7 +3428,7 @@ describe( 'URL validation', () => {
await waitFor(
() => {
expect(
screen.getByText( 'Please enter a valid URL.' )
within( container ).getByText( 'Please enter a valid URL.' )
).toBeVisible();
},
{ timeout: 100 }
Expand All @@ -3441,7 +3441,7 @@ describe( 'URL validation', () => {
it( 'should show validation error when pressing Enter to submit with an invalid URL', async () => {
// When editing an existing link, use Apply button
const existingLink = { url: 'https://example.com', title: 'Example' };
render(
const { container } = render(
<LinkControl
value={ existingLink }
forceIsEditingLink
Expand All @@ -3460,7 +3460,7 @@ describe( 'URL validation', () => {
await waitFor(
() => {
expect(
screen.getByText( 'Please enter a valid URL.' )
within( container ).getByText( 'Please enter a valid URL.' )
).toBeVisible();
},
{ timeout: 100 }
Expand Down
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- `ControlWithError`: Remove redundant inline aria-live region for validation error messages to prevent duplicate announcements. ([#79600](https://github.com/WordPress/gutenberg/pull/79600)).

### Breaking Changes

- `ExternalLink`: No longer sets the `rel` attribute by default. Consumers relying on the previous behavior should pass `rel` explicitly ([#79743](https://github.com/WordPress/gutenberg/pull/79743)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ function UnforwardedControlWithError< C extends React.ReactElement >(
),
required,
} ) }
<div aria-live="polite">{ visibleMessage }</div>
{ visibleMessage }
</div>
);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

- Widen React peer dependency ranges to `^18 || ^19` to support both React 18 and React 19 environments ([#80024](https://github.com/WordPress/gutenberg/pull/80024)).

- `ValidatedDateControl`: Update validation error message to be announced by screen readers using `speak` instead of an inline `aria-live` region ([#79600](https://github.com/WordPress/gutenberg/pull/79600))

## 17.1.0 (2026-07-01)

## 17.0.0 (2026-06-24)
Expand Down
1 change: 1 addition & 0 deletions packages/dataviews/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"sideEffects": false,
"dependencies": {
"@ariakit/react": "^0.4.32",
"@wordpress/a11y": "file:../a11y",
"@wordpress/base-styles": "file:../base-styles",
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
Expand Down
45 changes: 25 additions & 20 deletions packages/dataviews/src/components/dataform-controls/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
privateApis as componentsPrivateApis,
__experimentalInputControl as InputControl,
} from '@wordpress/components';
import { speak } from '@wordpress/a11y';
import {
useCallback,
useEffect,
Expand Down Expand Up @@ -241,6 +242,12 @@ function ValidatedDateControl< Item >( {
}
}, [ isTouched, isValid, validity, validateRefs ] );

useEffect( () => {
if ( isTouched && customValidity?.message ) {
speak( customValidity.message );
}
}, [ isTouched, customValidity?.message ] );

const onBlur = ( event: React.FocusEvent< HTMLDivElement > ) => {
if ( isTouched ) {
return;
Expand All @@ -259,26 +266,24 @@ function ValidatedDateControl< Item >( {
return (
<div onBlur={ onBlur }>
{ children }
<div aria-live="polite">
{ 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>
) }
</div>
{ 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>
) }
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/dataviews/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
]
},
"references": [
{ "path": "../a11y" },
{ "path": "../components" },
{ "path": "../compose" },
{ "path": "../data" },
Expand Down
2 changes: 2 additions & 0 deletions packages/fields/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- Widen React peer dependency ranges to `^18 || ^19` to support both React 18 and React 19 environments ([#80024](https://github.com/WordPress/gutenberg/pull/80024)).

- `MediaEdit`: Update validation error message to be announced by screen readers. ([#79600](https://github.com/WordPress/gutenberg/pull/79600))

## 0.42.0 (2026-07-01)

## 0.41.0 (2026-06-24)
Expand Down
1 change: 1 addition & 0 deletions packages/fields/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
],
"dependencies": {
"@react-spring/web": "^9.4.5",
"@wordpress/a11y": "file:../a11y",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/base-styles": "file:../base-styles",
"@wordpress/blob": "file:../blob",
Expand Down
44 changes: 25 additions & 19 deletions packages/fields/src/components/media-edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
chevronRight,
} from '@wordpress/icons';
import { VisuallyHidden, Tooltip } from '@wordpress/ui';
import { speak } from '@wordpress/a11y';
import {
MediaUpload,
uploadMedia,
Expand Down Expand Up @@ -838,6 +839,13 @@ export default function MediaEdit< Item >( {
setCustomValidity( undefined );
}
}, [ isTouched, field.isValid, validity ] );

useEffect( () => {
if ( isTouched && customValidity?.message ) {
speak( customValidity.message );
}
}, [ isTouched, customValidity?.message ] );

const onBlur = useCallback(
( event: React.FocusEvent< HTMLElement > ) => {
if ( isTouched ) {
Expand Down Expand Up @@ -961,25 +969,23 @@ export default function MediaEdit< Item >( {
/>
</VisuallyHidden>
{ customValidity && (
<div aria-live="polite">
<p
className={ clsx(
'components-validated-control__indicator',
{
'is-invalid': customValidity.type === 'invalid',
'is-valid': customValidity.type === 'valid',
}
) }
>
<WCIcon
className="components-validated-control__indicator-icon"
icon={ errorIcon }
size={ 16 }
fill="currentColor"
/>
{ customValidity.message }
</p>
</div>
<p
className={ clsx(
'components-validated-control__indicator',
{
'is-invalid': customValidity.type === 'invalid',
'is-valid': customValidity.type === 'valid',
}
) }
>
<WCIcon
className="components-validated-control__indicator-icon"
icon={ errorIcon }
size={ 16 }
fill="currentColor"
/>
{ customValidity.message }
</p>
) }
</div>
);
Expand Down
1 change: 1 addition & 0 deletions packages/fields/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"checkJs": false
},
"references": [
{ "path": "../a11y" },
{ "path": "../api-fetch" },
{ "path": "../blob" },
{ "path": "../components" },
Expand Down
2 changes: 2 additions & 0 deletions packages/media-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- Widen React peer dependency ranges to `^18 || ^19` to support both React 18 and React 19 environments ([#80024](https://github.com/WordPress/gutenberg/pull/80024)).

- `useAriaAnnouncer`: Update to use `speak` for screen reader announcements instead of an inline `aria-live` region. ([#79600](https://github.com/WordPress/gutenberg/pull/79600))

## 0.13.0 (2026-07-01)

## 0.12.0 (2026-06-24)
Expand Down
1 change: 1 addition & 0 deletions packages/media-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"build-module/store/index.mjs"
],
"dependencies": {
"@wordpress/a11y": "file:../a11y",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/base-styles": "file:../base-styles",
"@wordpress/components": "file:../components",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,9 @@ function CropperInner(
onStateChange?.( state );
}, [ state, onStateChange ] );

// ARIA live region: announce significant state changes for screen readers.
const ariaMessage = useAriaAnnouncer( state );
// ARIA announcements: announce significant state changes for screen readers
// via the centralized @wordpress/a11y speak() API called inside the hook.
useAriaAnnouncer( state );

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 tested these changes in the cropper component and they LGTM. Things are still announced as expected.


// Compute fitted image dimensions and visual bounds from camera math.
const naturalWidth = state.image?.naturalWidth ?? 0;
Expand Down Expand Up @@ -1107,16 +1108,6 @@ function CropperInner(
/>
) }
</div>

{ /* ARIA live region for screen reader announcements */ }
<div
aria-live="polite"
aria-atomic="true"
className="wp-media-editor-image-editor__aria-live"
style={ VISUALLY_HIDDEN_STYLE }
>
{ ariaMessage }
</div>
</div>
</div>
);
Expand Down
Loading
Loading