Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

SVG primitive: Support custom aria-hidden value #21173

Closed
wants to merge 1 commit into from

Conversation

WunderBart
Copy link
Member

@WunderBart WunderBart commented Mar 26, 2020

Description

This PR introduces support for providing a custom aria-hidden attribute for the SVG primitive.

Currently, the SVG component has the aria-hidden value fixed to true. This is fine for decorative icons that are used within a button or a link to other interactive controls. Unfortunately, this prevents us from using ARIA when we need to enhance SVG accessibility. E.g., if we wanted to add the <title> element inside SVG as suggested here, it wouldn't take any effect because of the hardcoded aria-hidden="true" presence.

Related discussion: https://wordpress.slack.com/archives/C02RP4X03/p1585161710006700

How has this been tested?

If explicitly specified, the aria-hidden prop value should be passed as is to the aria-hidden SVG attribute. This means that the undefined will also be accepted, resulting in the removal of that attribute.

⚠️ Note:
undefined should be the preferred way over "false" as it's reported to behave inconsistently across browsers.

Types of changes

New feature (non-breaking change which adds functionality)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@github-actions
Copy link

github-actions bot commented Mar 26, 2020

Size Change: +15 B (0%)

Total Size: 857 kB

Filename Size Change
build/primitives/index.js 1.51 kB +15 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 998 B 0 B
build/annotations/index.js 3.43 kB 0 B
build/api-fetch/index.js 3.39 kB 0 B
build/autop/index.js 2.58 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 6.02 kB 0 B
build/block-directory/style-rtl.css 760 B 0 B
build/block-directory/style.css 760 B 0 B
build/block-editor/index.js 101 kB 0 B
build/block-editor/style-rtl.css 10.9 kB 0 B
build/block-editor/style.css 10.9 kB 0 B
build/block-library/editor-rtl.css 7.22 kB 0 B
build/block-library/editor.css 7.23 kB 0 B
build/block-library/index.js 110 kB 0 B
build/block-library/style-rtl.css 7.43 kB 0 B
build/block-library/style.css 7.44 kB 0 B
build/block-library/theme-rtl.css 669 B 0 B
build/block-library/theme.css 671 B 0 B
build/block-serialization-default-parser/index.js 1.65 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/blocks/index.js 57.5 kB 0 B
build/components/index.js 191 kB 0 B
build/components/style-rtl.css 15.8 kB 0 B
build/components/style.css 15.7 kB 0 B
build/compose/index.js 6.21 kB 0 B
build/core-data/index.js 10.6 kB 0 B
build/data-controls/index.js 1.04 kB 0 B
build/data/index.js 8.25 kB 0 B
build/date/index.js 5.37 kB 0 B
build/deprecated/index.js 771 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.06 kB 0 B
build/edit-post/index.js 91.2 kB 0 B
build/edit-post/style-rtl.css 8.43 kB 0 B
build/edit-post/style.css 8.43 kB 0 B
build/edit-site/index.js 6.73 kB 0 B
build/edit-site/style-rtl.css 2.91 kB 0 B
build/edit-site/style.css 2.9 kB 0 B
build/edit-widgets/index.js 4.43 kB 0 B
build/edit-widgets/style-rtl.css 2.57 kB 0 B
build/edit-widgets/style.css 2.57 kB 0 B
build/editor/editor-styles-rtl.css 428 B 0 B
build/editor/editor-styles.css 431 B 0 B
build/editor/index.js 42.8 kB 0 B
build/editor/style-rtl.css 3.38 kB 0 B
build/editor/style.css 3.38 kB 0 B
build/element/index.js 4.44 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/index.js 6.95 kB 0 B
build/format-library/style-rtl.css 502 B 0 B
build/format-library/style.css 502 B 0 B
build/hooks/index.js 1.93 kB 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 3.49 kB 0 B
build/is-shallow-equal/index.js 710 B 0 B
build/keyboard-shortcuts/index.js 2.3 kB 0 B
build/keycodes/index.js 1.69 kB 0 B
build/list-reusable-blocks/index.js 2.99 kB 0 B
build/list-reusable-blocks/style-rtl.css 226 B 0 B
build/list-reusable-blocks/style.css 226 B 0 B
build/media-utils/index.js 4.84 kB 0 B
build/notices/index.js 1.57 kB 0 B
build/nux/index.js 3.01 kB 0 B
build/nux/style-rtl.css 616 B 0 B
build/nux/style.css 613 B 0 B
build/plugins/index.js 2.54 kB 0 B
build/priority-queue/index.js 781 B 0 B
build/redux-routine/index.js 2.84 kB 0 B
build/rich-text/index.js 14.5 kB 0 B
build/server-side-render/index.js 2.55 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 4.01 kB 0 B
build/viewport/index.js 1.61 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.18 kB 0 B

compressed-size-action

@WunderBart WunderBart changed the title SVG primitive: Make disabling of aria-hidden possible SVG primitive: Support custom aria-hidden value Mar 26, 2020
@WunderBart WunderBart self-assigned this Mar 26, 2020
@WunderBart WunderBart added [Package] Components /packages/components [Type] Enhancement A suggestion for improvement. Needs Accessibility Feedback Need input from accessibility labels Mar 26, 2020
@WunderBart WunderBart marked this pull request as ready for review March 26, 2020 14:19
@WunderBart WunderBart requested review from jeryj, afercia and gziolo March 26, 2020 14:19
@WunderBart
Copy link
Member Author

I noticed that the React Native implementation does not force the same attributes (role, aria-hidden & focusable). Is that intended?

@@ -26,7 +26,9 @@ export const SVG = ( { className, isPressed, ...props } ) => {
className:
classnames( className, { 'is-pressed': isPressed } ) || undefined,
role: 'img',
'aria-hidden': 'true',
'aria-hidden': props.hasOwnProperty( 'aria-hidden' )
Copy link
Member

@gziolo gziolo Mar 26, 2020

Choose a reason for hiding this comment

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

Did you consider using a new prop like isFocusable that defaults to false to make it more explicit? ARIA is web-specific, it doesn't exist in React Native.

Copy link
Member Author

Choose a reason for hiding this comment

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

My focus for this PR was only the aria-hidden attribute. Nevertheless, if we'd like to make focusable attribute optional with default value set, wouldn't it be better if we move it above the props spread instead of creating custom isFocusable prop? E.g.

export const SVG = ( { className, isPressed, ...props } ) => (
	<svg
		className={
			classnames( className, { 'is-pressed': isPressed } ) || undefined
		}
		role="img"
		aria-hidden="true"
		focusable="false"
		{ ...props }
	/>
);

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it would help in this case. I'm not against this change, I just wanted to raise the awareness of the React Native and all the challenges it creates.

@aduth
Copy link
Member

aduth commented Mar 26, 2020

Can we just change the order of the object spread so that ...props takes precedent? This would have the same effect, effectively allowing a developer to pass their own aria-hidden to override.

I believe the idea with those hard-coded values is to serve as smart defaults. But they should be just that: defaults. I don't know that we need be so opinionated that they can't be overridden.

@jeryj
Copy link
Contributor

jeryj commented Mar 26, 2020

@aduth

Can we just change the order of the object spread so that ...props takes precedent?

I think that's a great idea 🙌 I'm not sure how best to handle the className one though. Should it stay after ...props since it gets handled in a different way with the explicit className and isPressed props?

Currently:

const appliedProps = {
	...props,
	className: classnames( className, { 'is-pressed': isPressed } ) || undefined,
	role: 'img',
	'aria-hidden': 'true',
	focusable: 'false',
};

Proposed change to move ...props lower:

const appliedProps = {
	 role: 'img',
	'aria-hidden': 'true',
	focusable: 'false',
	...props,
	className: classnames( className, { 'is-pressed': isPressed } ) || undefined,
};

@aduth
Copy link
Member

aduth commented Mar 26, 2020

Proposed change to move ...props lower:

const appliedProps = {
	 role: 'img',
	'aria-hidden': 'true',
	focusable: 'false',
	...props,
	className: classnames( className, { 'is-pressed': isPressed } ) || undefined,
};

Yes, I believe your assessment here is correct. It should be last, in order to both retain the passed prop value and include additional class names specific to the component.

@aduth
Copy link
Member

aduth commented Mar 26, 2020

Sorry, my last comment is wrong. It doesn't need to be last. Since className is explicitly picked, the ...props spread value wouldn't contain it anyways, so it wouldn't override if allowed to be last.

@aduth
Copy link
Member

aduth commented Mar 26, 2020

Related to the ongoing Slack conversation linked in the original comment, it would be good to have demonstrated use-cases for what this is intended to be used for, not simply the fact that it would be needed if there was a hypothetical need to use the <title> tag.

Also note that aside from smart defaults, there is a secondary purpose of this component, which is mobile compatibility. My understanding is that usage of the default <svg> tag in React is not compatible in a native environment, and that this is addressed through this component in its own implementation using a shim.

@gziolo
Copy link
Member

gziolo commented Mar 26, 2020

Also note that aside from smart defaults, there is a secondary purpose of this component, which is mobile compatibility. My understanding is that usage of the default <svg> tag in React is not compatible in a native environment, and that this is addressed through this component in its own implementation using a shim.

All HTML tags and their attributes aren’t supported by React Native. See my comment: #21173 (comment). We should try to avoid platform specific language in the package containing primitives.

@aduth
Copy link
Member

aduth commented Mar 26, 2020

Also note that aside from smart defaults, there is a secondary purpose of this component, which is mobile compatibility. My understanding is that usage of the default <svg> tag in React is not compatible in a native environment, and that this is addressed through this component in its own implementation using a shim.

All HTML tags and their attributes aren’t supported by React Native. See my comment: #21173 (comment). We should try to avoid platform specific language in the package containing primitives.

Ah, right, I think I was making bad interpretations of the intent behind previous efforts of #9685 and #9294.

@WunderBart
Copy link
Member Author

WunderBart commented Mar 27, 2020

Thanks for reviewing, @gziolo @aduth @jeryj 🙌

Related to the ongoing Slack conversation linked in the original comment, it would be good to have demonstrated use-cases for what this is intended to be used for, not simply the fact that it would be needed if there was a hypothetical need to use the <title> tag.

By the time I created this PR we had a use-case for the Podcast Player block we're implementing currently in Jetpack. In the tracklist, we display a status icon for the active track:

image
Currently, we support 2 states: playing and error (paused too, but it has no icon ATM). We wanted these states to be announced to the user, e.g.:

“Playing - [track name], selected”

instead of just

“[track name], selected”

To achieve that we chose to include the <title> element inside, as suggested here. It seemed to be the right way at the time but it turns out there's a potential issue in allowing this - "developers adding titles where not necessary".

The alternative approach in our case was adding the visually hidden text outside the SVG, e.g.:

<span class="visually-hidden">Playing: </span><svg aria-hidden="true" /> [track name]

...which we eventually decided to continue with (p1585251445460300-slack-ajax).


TL;DR, since we're not going to continue with the title approach and because of the mentioned concerns this way involves, I'm happy to close this PR.

cc @afercia @enriquesanchez

@afercia
Copy link
Contributor

afercia commented Mar 27, 2020

Related to the ongoing Slack conversation linked in the original comment, it would be good to have demonstrated use-cases for what this is intended to be used for, not simply the fact that it would be needed if there was a hypothetical need to use the <title> tag.

I'd second this.

By the time I created this PR we had a use-case for the Podcast Player block we're implementing currently in Jetpack. In the tracklist, we display a status icon for the active track:

The alternative approach with visually hidden text seems better to me, as icons are not appropriate to communicate a state in the first place. I'd agree with @WunderBart's last comment and suggest close.

Aside: re: the focusable attribute, worth reminding it's only used to fix an IE11 bug, where SVG icons within buttons or links become focusable themselves, thus requiring a double Tab key press to keyboard users.

@WunderBart WunderBart closed this Mar 30, 2020
@WunderBart WunderBart deleted the update/svg-a11y branch March 30, 2020 16:50
@WunderBart WunderBart removed their assignment Mar 30, 2020
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Needs Accessibility Feedback Need input from accessibility [Package] Components /packages/components [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants