Skip to content

Commit bcc4ea3

Browse files
jeryjgetdave
authored andcommitted
Fix a11y of descriptions and alerts for "Invalid" Nav Items (#73177)
The role="alert" and aria-live="polite" attributes were causing the error message to be announced twice to screen readers. Since this text is referenced via aria-describedby on the input field, it will be read when users focus the field - no live announcement is needed. This also adds aria-invalid and aria-describedBy to the navigation link block if the synced entity is not found. This allows screen reader users to be aware of the issue on canvas as well. --------- Co-authored-by: Dave Smith <getdavemail@gmail.com>
1 parent e7f0dea commit bcc4ea3

File tree

3 files changed

+41
-22
lines changed

3 files changed

+41
-22
lines changed

packages/block-library/src/navigation-link/edit.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import clsx from 'clsx';
88
*/
99
import { createBlock } from '@wordpress/blocks';
1010
import { useSelect, useDispatch } from '@wordpress/data';
11-
import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
11+
import {
12+
ToolbarButton,
13+
ToolbarGroup,
14+
VisuallyHidden,
15+
} from '@wordpress/components';
1216
import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes';
13-
import { __ } from '@wordpress/i18n';
17+
import { __, sprintf } from '@wordpress/i18n';
1418
import {
1519
BlockControls,
1620
InspectorControls,
@@ -26,13 +30,19 @@ import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
2630
import { decodeEntities } from '@wordpress/html-entities';
2731
import { link as linkIcon, addSubmenu } from '@wordpress/icons';
2832
import { store as coreStore } from '@wordpress/core-data';
29-
import { useMergeRefs, usePrevious } from '@wordpress/compose';
33+
import { useMergeRefs, usePrevious, useInstanceId } from '@wordpress/compose';
3034

3135
/**
3236
* Internal dependencies
3337
*/
3438
import { getColors } from '../navigation/edit/utils';
35-
import { Controls, LinkUI, updateAttributes, useEntityBinding } from './shared';
39+
import {
40+
Controls,
41+
LinkUI,
42+
updateAttributes,
43+
useEntityBinding,
44+
MissingEntityHelpText,
45+
} from './shared';
3646

3747
const DEFAULT_BLOCK = { name: 'core/navigation-link' };
3848
const NESTING_BLOCK_NAMES = [
@@ -394,6 +404,12 @@ export default function NavigationLinkEdit( {
394404
}
395405
}
396406

407+
const instanceId = useInstanceId( NavigationLinkEdit );
408+
const hasMissingEntity = hasUrlBinding && ! isBoundEntityAvailable;
409+
const missingEntityDescriptionId = hasMissingEntity
410+
? sprintf( 'navigation-link-edit-%d-desc', instanceId )
411+
: undefined;
412+
397413
const blockProps = useBlockProps( {
398414
ref: useMergeRefs( [ setPopoverAnchor, listItemRef ] ),
399415
className: clsx( 'wp-block-navigation-item', {
@@ -407,6 +423,8 @@ export default function NavigationLinkEdit( {
407423
[ getColorClassName( 'background-color', backgroundColor ) ]:
408424
!! backgroundColor,
409425
} ),
426+
'aria-describedby': missingEntityDescriptionId,
427+
'aria-invalid': hasMissingEntity,
410428
style: {
411429
color: ! textColor && customTextColor,
412430
backgroundColor: ! backgroundColor && customBackgroundColor,
@@ -482,6 +500,11 @@ export default function NavigationLinkEdit( {
482500
/>
483501
</InspectorControls>
484502
<div { ...blockProps }>
503+
{ hasMissingEntity && (
504+
<VisuallyHidden id={ missingEntityDescriptionId }>
505+
<MissingEntityHelpText type={ type } kind={ kind } />
506+
</VisuallyHidden>
507+
) }
485508
{ /* eslint-disable jsx-a11y/anchor-is-valid */ }
486509
<a className={ classes }>
487510
{ /* eslint-enable */ }

packages/block-library/src/navigation-link/shared/controls.js

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
221221
} }
222222
help={
223223
hasUrlBinding && ! isBoundEntityAvailable ? (
224-
<MissingEntityHelpText
224+
<MissingEntityHelp
225225
id={ helpTextId }
226226
type={ attributes.type }
227227
kind={ attributes.kind }
@@ -327,7 +327,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
327327
* @param {string} props.kind - The entity kind
328328
* @return {string} Help text for the bound URL
329329
*/
330-
function BindingHelpText( { type, kind } ) {
330+
export function BindingHelpText( { type, kind } ) {
331331
const entityType = getEntityTypeName( type, kind );
332332
return sprintf(
333333
/* translators: %s is the entity type (e.g., "page", "post", "category") */
@@ -340,27 +340,23 @@ function BindingHelpText( { type, kind } ) {
340340
* Component to display error help text for missing entity bindings.
341341
*
342342
* @param {Object} props - Component props
343-
* @param {string} props.id - ID for the help text element (for aria-describedby)
344343
* @param {string} props.type - The entity type
345344
* @param {string} props.kind - The entity kind
346345
* @return {JSX.Element} Error help text component
347346
*/
348-
function MissingEntityHelpText( { id, type, kind } ) {
347+
export function MissingEntityHelpText( { type, kind } ) {
349348
const entityType = getEntityTypeName( type, kind );
349+
return sprintf(
350+
/* translators: %s is the entity type (e.g., "page", "post", "category") */
351+
__( 'Synced %s is missing. Please update or remove this link.' ),
352+
entityType
353+
);
354+
}
355+
356+
function MissingEntityHelp( { id, type, kind } ) {
350357
return (
351-
<span
352-
id={ id }
353-
className="navigation-link-control__error-text"
354-
role="alert"
355-
aria-live="polite"
356-
>
357-
{ sprintf(
358-
/* translators: %s is the entity type (e.g., "page", "post", "category") */
359-
__(
360-
'Synced %s is missing. Please update or remove this link.'
361-
),
362-
entityType
363-
) }
358+
<span id={ id } className="navigation-link-control__error-text">
359+
<MissingEntityHelpText type={ type } kind={ kind } />
364360
</span>
365361
);
366362
}

packages/block-library/src/navigation-link/shared/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* to reduce code duplication and ensure consistent behavior.
66
*/
77

8-
export { Controls } from './controls';
8+
export { Controls, BindingHelpText, MissingEntityHelpText } from './controls';
99
export { updateAttributes } from './update-attributes';
1010
export {
1111
useEntityBinding,

0 commit comments

Comments
 (0)