Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Cover: Fix placeholder color options keyboard accessibility #68662

Open
wants to merge 10 commits into
base: trunk
Choose a base branch
from

Conversation

afercia
Copy link
Contributor

@afercia afercia commented Jan 14, 2025

Fixes #68639

What?

  • The color options in the Cover block placeholder can't be fully operated with the keyboard.
  • When the circular options picker is rendered with asButtons set to true, it would be best for it to have a role=group and appropriate labeling.

Why?

  • Any UI must fully work with the keyboard.
  • Labeling is essential to communicate assistive technology users what an UI is about.

How?

In the fist commit

Setting asButtons to true makes the circular options picker render the buttons as a a list of buttons that are all focusable and tabbables. This basically removes the arrow keys navigation that is in use when the options are rendered as a listbox, which conflicts with WritingFlow.

In the second commit.

I just wanted to set a role="group" and a meaningful aria-label when asButtons is true. This is important because otherwise the options are just a series of buttons. Adding a role="group" to the wrapper makes them logically grouped and the aria-label communicates users what this group of buttons is about.

However, I discovered that adding aria-label didn't render an aria-label attribute when asButtons is true. Turned out the aria-label and aria-labelledby props were only allowed for the ListboxCircularOptionPicker and not for ButtonsCircularOptionPicker.

I'm not that familiar with TypeScript and I had to make a series of changes to make this work that honestly I don't fully understand. I would greatly appreciate some eyes from contributors more familiar than me with TypeScript.
Note that I had to make changes to the allowed types also for the components that consume CircularOptionPicker which are: ColorPalette, DuotonePicker and GradientPicker.

To recap;
on trunk, by default the circular option picker renders:

  • a role="listbox"
  • a default aria-label="Custom color picker." that cab be passed a custom aria-label

Instead, when rendered as normal buttons via asButton, it renders:

  • no role
  • passing an aria-label doesn't work

I just want the asButtons version have a role=group and an aria-label.

Note: to my understanding, this old issue #54055 was aiming to provide a better labeling mechanism for these components but it seems it still needs to be addressed. It would be nice to consider further improvements in the context of that issue.

Testing Instructions

Testing Instructions for Keyboard

Screenshots or screencast

Before After

@afercia afercia added [Type] Bug An existing feature does not function as intended [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Package] Block library /packages/block-library [Block] Cover Affects the Cover Block - used to display content laid over a background image labels Jan 14, 2025
@afercia afercia marked this pull request as ready for review January 14, 2025 14:53
Copy link

github-actions bot commented Jan 14, 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: afercia <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: Mamaduka <[email protected]>
Co-authored-by: carolinan <[email protected]>
Co-authored-by: Mayank-Tripathi32 <[email protected]>

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

@afercia afercia added the [Package] Components /packages/components label Jan 14, 2025
Copy link

github-actions bot commented Jan 14, 2025

Size Change: -6 B (0%)

Total Size: 1.84 MB

Filename Size Change
build/block-library/index.min.js 225 kB +18 B (+0.01%)
build/components/index.min.js 229 kB -24 B (-0.01%)
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 447 B
build-module/block-library/form/view.min.js 533 B
build-module/block-library/image/view.min.js 1.77 kB
build-module/block-library/navigation/view.min.js 1.16 kB
build-module/block-library/query/view.min.js 742 B
build-module/block-library/search/view.min.js 616 B
build-module/interactivity-router/index.min.js 2.57 kB
build-module/interactivity/debug.min.js 17.3 kB
build-module/interactivity/index.min.js 13.7 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.26 kB
build/api-fetch/index.min.js 2.4 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.13 kB
build/block-directory/style-rtl.css 1.03 kB
build/block-directory/style.css 1.03 kB
build/block-editor/content-rtl.css 4.42 kB
build/block-editor/content.css 4.41 kB
build/block-editor/default-editor-styles-rtl.css 394 B
build/block-editor/default-editor-styles.css 394 B
build/block-editor/index.min.js 263 kB
build/block-editor/style-rtl.css 15.9 kB
build/block-editor/style.css 15.9 kB
build/block-library/blocks/archives/editor-rtl.css 84 B
build/block-library/blocks/archives/editor.css 83 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 555 B
build/block-library/blocks/button/style.css 555 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 345 B
build/block-library/blocks/buttons/style.css 345 B
build/block-library/blocks/calendar/style-rtl.css 240 B
build/block-library/blocks/calendar/style.css 240 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 191 B
build/block-library/blocks/comment-template/style.css 191 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 238 B
build/block-library/blocks/comments-pagination/editor.css 231 B
build/block-library/blocks/comments-pagination/style-rtl.css 245 B
build/block-library/blocks/comments-pagination/style.css 241 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 842 B
build/block-library/blocks/comments/editor.css 842 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 279 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 349 B
build/block-library/blocks/form-input/style.css 349 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/freeform/editor-rtl.css 2.59 kB
build/block-library/blocks/freeform/editor.css 2.59 kB
build/block-library/blocks/gallery/editor-rtl.css 688 B
build/block-library/blocks/gallery/editor.css 691 B
build/block-library/blocks/gallery/style-rtl.css 1.83 kB
build/block-library/blocks/gallery/style.css 1.82 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 346 B
build/block-library/blocks/html/editor.css 347 B
build/block-library/blocks/image/editor-rtl.css 799 B
build/block-library/blocks/image/editor.css 799 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 520 B
build/block-library/blocks/latest-posts/style.css 520 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 552 B
build/block-library/blocks/media-text/style.css 550 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 671 B
build/block-library/blocks/navigation-link/editor.css 672 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.2 kB
build/block-library/blocks/navigation/editor.css 2.2 kB
build/block-library/blocks/navigation/style-rtl.css 2.24 kB
build/block-library/blocks/navigation/style.css 2.23 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 378 B
build/block-library/blocks/page-list/editor.css 378 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 251 B
build/block-library/blocks/paragraph/editor.css 251 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/editor-rtl.css 107 B
build/block-library/blocks/post-author/editor.css 107 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-comments-link/style-rtl.css 71 B
build/block-library/blocks/post-comments-link/style.css 71 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 722 B
build/block-library/blocks/post-featured-image/editor.css 720 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/style-rtl.css 414 B
build/block-library/blocks/post-template/style.css 414 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 134 B
build/block-library/blocks/pullquote/editor.css 134 B
build/block-library/blocks/pullquote/style-rtl.css 351 B
build/block-library/blocks/pullquote/style.css 350 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query-total/style-rtl.css 64 B
build/block-library/blocks/query-total/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 404 B
build/block-library/blocks/query/editor.css 404 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 131 B
build/block-library/blocks/read-more/style.css 131 B
build/block-library/blocks/rss/editor-rtl.css 126 B
build/block-library/blocks/rss/editor.css 126 B
build/block-library/blocks/rss/style-rtl.css 284 B
build/block-library/blocks/rss/style.css 283 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 660 B
build/block-library/blocks/search/style.css 658 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 773 B
build/block-library/blocks/site-logo/editor.css 770 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 309 B
build/block-library/blocks/social-link/editor.css 309 B
build/block-library/blocks/social-links/editor-rtl.css 690 B
build/block-library/blocks/social-links/editor.css 688 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.51 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 92 B
build/block-library/blocks/tag-cloud/editor.css 92 B
build/block-library/blocks/tag-cloud/style-rtl.css 266 B
build/block-library/blocks/tag-cloud/style.css 265 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 441 B
build/block-library/blocks/video/editor.css 442 B
build/block-library/blocks/video/style-rtl.css 192 B
build/block-library/blocks/video/style.css 192 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.4 kB
build/block-library/editor.css 11.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 708 B
build/block-library/theme.css 712 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.6 kB
build/commands/index.min.js 16.2 kB
build/commands/style-rtl.css 955 B
build/commands/style.css 952 B
build/components/style-rtl.css 12.5 kB
build/components/style.css 12.5 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.3 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.43 kB
build/customize-widgets/style.css 1.43 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.69 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.67 kB
build/edit-post/classic-rtl.css 578 B
build/edit-post/classic.css 580 B
build/edit-post/index.min.js 13.4 kB
build/edit-post/style-rtl.css 2.74 kB
build/edit-post/style.css 2.73 kB
build/edit-site/index.min.js 221 kB
build/edit-site/posts-rtl.css 7.5 kB
build/edit-site/posts.css 7.5 kB
build/edit-site/style-rtl.css 13.6 kB
build/edit-site/style.css 13.6 kB
build/edit-widgets/index.min.js 17.6 kB
build/edit-widgets/style-rtl.css 4.05 kB
build/edit-widgets/style.css 4.06 kB
build/editor/index.min.js 116 kB
build/editor/style-rtl.css 9.3 kB
build/editor/style.css 9.31 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.05 kB
build/format-library/style-rtl.css 476 B
build/format-library/style.css 476 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 445 B
build/i18n/index.min.js 3.58 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 852 B
build/list-reusable-blocks/style.css 852 B
build/media-utils/index.min.js 3.61 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 767 B
build/nux/style.css 763 B
build/patterns/index.min.js 7.37 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 554 B
build/preferences/style.css 554 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 978 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.55 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.3 kB
build/router/index.min.js 5.42 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.9 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

@afercia
Copy link
Contributor Author

afercia commented Jan 23, 2025

Since any UI in the editor should be operable with the keyboard at least for a basic usage, and this UI isn't, I would appreciate this PR to be considered for the next WordPress release. Cc @WordPress/gutenberg-core
Note: I'm not sure this change really needs a changelog entry for the components package.

@Mamaduka Mamaduka changed the title Fix/cover placeholder color options keyboard accessibility Cover: Fix placeholder color options keyboard accessibility Jan 27, 2025
@Mamaduka
Copy link
Member

Pinging @WordPress/gutenberg-components as well for feedback.

Copy link
Contributor

@ciampo ciampo 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, @afercia .

I think it would be best to split the changes to a few separate PR:

  • one improving the accessibility (grouping and labelling CircularOptionPicker options when asButtons)
  • another one switching the Cover block placeholder to use the asButtons prop

It makes code changes easier to follow and test in isolation, especially when they happen across separate packages.

Comment on lines 43 to 52
} & (
| {
'aria-label': string;
'aria-labelledby'?: never;
}
| {
'aria-label'?: never;
'aria-labelledby': string;
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems technically correct, but let's watch out for TS computed types since combining type "unions" can quickly result in a high number or resulting types

Copy link
Contributor Author

@afercia afercia Jan 29, 2025

Choose a reason for hiding this comment

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

First time for me familiarizing with 'unions' so any advice is more than welcome.
Also, there's a little bit of duplication in this PR, I'd appreciate any feedback about how to prevent it and abstract a bit.

..._metaProps,
'aria-label': __( 'Custom color picker.' ),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The whole logic for building the metaProps object feels more complicated than it could be, potentially with less if statements and without using an intermediate _metaProps object.

I don't have much to take a look at it today, but if needed I can try to help in the next days.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes totally agree, I know. I just followed the existing pattern but I did see there's room for improvements. Also, there's now some duplication. I'd be glad if you want to make improvements here and make this PR yours, when you have a chance.

Comment on lines 134 to 154
| { asButtons: true; 'aria-label': string }
| { asButtons: true; 'aria-labelledby': string };

if ( asButtons ) {
metaProps = { asButtons: true };
const _metaProps: { asButtons: true } = {
asButtons: true,
};

if ( ariaLabel ) {
metaProps = { ..._metaProps, 'aria-label': ariaLabel };
} else if ( ariaLabelledby ) {
metaProps = {
..._metaProps,
'aria-labelledby': ariaLabelledby,
};
} else {
metaProps = {
..._metaProps,
'aria-label': __( 'Custom color picker.' ),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I know that this was an existing issue before this PR, but it looks like we have the same exact logic of DuotonePicker repeated in GradientPicker — something to look into and understand if it's worth de-duping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes totally agree, as mentioned above. I was just unsure abou tthe best way to abstract it and de-dupe.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could do something like the following:

  • simplify the logic by splitting the accessible label computation from the asButtons+loop, which already enables a lot of de-duping
  • move the logic to a shared place, eg a utils file in the CircularOptionPicker folder
Example code changes:
diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx
index 68c4d339120..93f1dc6c576 100644
--- a/packages/components/src/color-palette/index.tsx
+++ b/packages/components/src/color-palette/index.tsx
@@ -39,6 +39,7 @@ import {
 	isMultiplePaletteArray,
 	normalizeColorValue,
 } from './utils';
+import { useComputeCircularOptionPickerCommonProps } from '../circular-option-picker/utils';
 
 extend( [ namesPlugin, a11yPlugin ] );
 
@@ -251,50 +252,15 @@ function UnforwardedColorPalette(
 		</CircularOptionPicker.ButtonAction>
 	);
 
-	let metaProps:
-		| { asButtons: false; loop?: boolean; 'aria-label': string }
-		| { asButtons: false; loop?: boolean; 'aria-labelledby': string }
-		| { asButtons: true; 'aria-label': string }
-		| { asButtons: true; 'aria-labelledby': string };
-
-	if ( asButtons ) {
-		const _metaProps: { asButtons: true } = {
-			asButtons: true,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
-		}
-	} else {
-		const _metaProps: { asButtons: false; loop?: boolean } = {
-			asButtons: false,
+	const { metaProps, labelProps } = useComputeCircularOptionPickerCommonProps(
+		{
+			asButtons,
 			loop,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
+			ariaLabel,
+			ariaLabelledby,
+			fallbackLabel: __( 'Custom color picker.' ),
 		}
-	}
+	);
 
 	return (
 		<VStack spacing={ 3 } ref={ forwardedRef } { ...additionalProps }>
@@ -352,6 +318,7 @@ function UnforwardedColorPalette(
 			{ ( colors.length > 0 || actions ) && (
 				<CircularOptionPicker
 					{ ...metaProps }
+					{ ...labelProps }
 					actions={ actions }
 					options={
 						hasMultipleColorOrigins ? (
diff --git a/packages/components/src/duotone-picker/duotone-picker.tsx b/packages/components/src/duotone-picker/duotone-picker.tsx
index ee8319b1618..f6348bf1129 100644
--- a/packages/components/src/duotone-picker/duotone-picker.tsx
+++ b/packages/components/src/duotone-picker/duotone-picker.tsx
@@ -20,6 +20,7 @@ import CustomDuotoneBar from './custom-duotone-bar';
 import { getDefaultColors, getGradientFromCSSColors } from './utils';
 import { Spacer } from '../spacer';
 import type { DuotonePickerProps } from './types';
+import { useComputeCircularOptionPickerCommonProps } from '../circular-option-picker/utils';
 
 /**
  * ```jsx
@@ -127,50 +128,15 @@ function DuotonePicker( {
 		);
 	} );
 
-	let metaProps:
-		| { asButtons: false; loop?: boolean; 'aria-label': string }
-		| { asButtons: false; loop?: boolean; 'aria-labelledby': string }
-		| { asButtons: true; 'aria-label': string }
-		| { asButtons: true; 'aria-labelledby': string };
-
-	if ( asButtons ) {
-		const _metaProps: { asButtons: true } = {
-			asButtons: true,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
-		}
-	} else {
-		const _metaProps: { asButtons: false; loop?: boolean } = {
-			asButtons: false,
+	const { metaProps, labelProps } = useComputeCircularOptionPickerCommonProps(
+		{
+			asButtons,
 			loop,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
+			ariaLabel,
+			ariaLabelledby,
+			fallbackLabel: __( 'Custom color picker.' ),
 		}
-	}
+	);
 
 	const options = unsetable
 		? [ unsetOption, ...duotoneOptions ]
@@ -180,6 +146,7 @@ function DuotonePicker( {
 		<CircularOptionPicker
 			{ ...otherProps }
 			{ ...metaProps }
+			{ ...labelProps }
 			options={ options }
 			actions={
 				!! clearable && (
diff --git a/packages/components/src/gradient-picker/index.tsx b/packages/components/src/gradient-picker/index.tsx
index 544faee8481..7d9ecdff8bd 100644
--- a/packages/components/src/gradient-picker/index.tsx
+++ b/packages/components/src/gradient-picker/index.tsx
@@ -18,6 +18,7 @@ import type {
 	OriginObject,
 	GradientObject,
 } from './types';
+import { useComputeCircularOptionPickerCommonProps } from '../circular-option-picker/utils';
 
 // The Multiple Origin Gradients have a `gradients` property (an array of
 // gradient objects), while Single Origin ones have a `gradient` property.
@@ -128,54 +129,20 @@ function Component( props: PickerProps< any > ) {
 		<SingleOrigin { ...additionalProps } />
 	);
 
-	let metaProps:
-		| { asButtons: false; loop?: boolean; 'aria-label': string }
-		| { asButtons: false; loop?: boolean; 'aria-labelledby': string }
-		| { asButtons: true; 'aria-label': string }
-		| { asButtons: true; 'aria-labelledby': string };
-
-	if ( asButtons ) {
-		const _metaProps: { asButtons: true } = {
-			asButtons: true,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
-		}
-	} else {
-		const _metaProps: { asButtons: false; loop?: boolean } = {
-			asButtons: false,
+	const { metaProps, labelProps } = useComputeCircularOptionPickerCommonProps(
+		{
+			asButtons,
 			loop,
-		};
-
-		if ( ariaLabel ) {
-			metaProps = { ..._metaProps, 'aria-label': ariaLabel };
-		} else if ( ariaLabelledby ) {
-			metaProps = {
-				..._metaProps,
-				'aria-labelledby': ariaLabelledby,
-			};
-		} else {
-			metaProps = {
-				..._metaProps,
-				'aria-label': __( 'Custom color picker.' ),
-			};
+			ariaLabel,
+			ariaLabelledby,
+			fallbackLabel: __( 'Custom color picker.' ),
 		}
-	}
+	);
 
 	return (
 		<CircularOptionPicker
 			{ ...metaProps }
+			{ ...labelProps }
 			actions={ actions }
 			options={ options }
 		/>

@afercia afercia force-pushed the fix/cover-placeholder-color-options-keyboard-accessibility branch 2 times, most recently from 013af9c to 3a78228 Compare February 7, 2025 09:45
@afercia afercia requested a review from ellatrix as a code owner February 7, 2025 09:45
/**
* Computes the common props for the CircularOptionPicker.
*/
export function useComputeCircularOptionPickerCommonProps(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export function useComputeCircularOptionPickerCommonProps(
export function getComputeCircularOptionPickerCommonProps(

Should we use the get prefix instead of use? The latter is a reserved prefix in React for hooks, but since this method isn't a hook, it does not need to adhere to the "Rules of Hook" enforced by ESLint/React.

@afercia
Copy link
Contributor Author

afercia commented Feb 7, 2025

I'm going to update this PR title because it's not specific to the Cover block, it's more generic to the CircularOptionPicker component and its usage in other components. I changed my mind :D

@@ -15,7 +15,7 @@ export function OptionGroup( {
}: OptionGroupProps ) {
const role =
'aria-label' in additionalProps || 'aria-labelledby' in additionalProps
? 'group'
? 'listbox'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This aims to fix an additional issue with the CircularOptionPicker.OptionGroup. To my understanding, it always contains buttons with a role="option" so it should be a listbox.
This is different from the behavior of the parent component CircularOptionPicker that changes role depending on the asButton prop, which actually renders two alternative components: ListboxCircularOptionPicker or ButtonsCircularOptionPicker.

Copy link
Contributor

Choose a reason for hiding this comment

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

it always contains buttons with a role="option" so it should be a listbox.

Not 100% about that, but for sure this component is already rendered inside an element with role="listbox", see this example:

Screenshot 2025-02-07 at 15 07 05

Copy link
Contributor Author

@afercia afercia Feb 7, 2025

Choose a reason for hiding this comment

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

Hm so this is intended to render one or more groups inside a listbox? Then I'm not understanding why the CircularOptionPicker in the ColorPalette is not renderinf the role=listbox.
To recapt:

  1. role=listbox > role=group > series of role=option is a valid structure
  2. role=group > series of role=option isn't.

I can revert this change but this should be investigated to understand how to fix the scenario 2).

Copy link
Contributor

@ciampo ciampo Feb 7, 2025

Choose a reason for hiding this comment

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

The example above is from ColorPalette
Screenshot 2025-02-07 at 15 51 00

Did you come across an instance of ColorPalette without a listbox parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mentioned on the issue: Paragraph block > Color > Text.
Notice there's also a difference beween single palette and multiple palette.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for signaling — I think it deserves a separate investigation, and I would otherwise leave the group role for the time being.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, will revert on Monday and create a separate issue. Thanks for the review.

@afercia
Copy link
Contributor Author

afercia commented Feb 7, 2025

I tried to do my best to abstract the computation of the props. Please do let me know if this PR needs a changelog entry for the components package. Not sure because it started as a fix for the Cover block but then required changes to:

  • CircularOptionPicker
  • ColorPalette
  • DuotonePicker
  • GradientPicker

I'd appreciate some eyes on the Typescript changes, really not my area of expertise. I tried to simplify. To recap:

  • asButtons and loop are now common props and they are optional
  • The mutual exclusivity of aria-labelledby and aria-label (only one of them should be rendered) is now in the getComputeCircularOptionPickerCommonProps logic rather than in the Typescript part. Both are now optional props, with an aria-label fallback.

@@ -132,7 +132,7 @@ function ButtonsCircularOptionPicker(
);

return (
<div { ...additionalProps } id={ baseId }>
<div { ...additionalProps } role="group" id={ baseId }>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ok to have a role="group" even when the element is not labelled? Should we add a check like in the OptionGroup component?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is OK. It should use the fallback label though, am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

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

It will when used in ColorPalette / DuotonePicker / GradientPicker, but when the CircularOptionPicker is used standalone, we're not guaranteed to have a fallback label, right?

Yes it is OK.

If it is OK, should we remove the equivalent check in OptionGroup ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A group role is a very generic role for signaling logically grouped controls. In a way, it's similar to a <fieldset> element that, without a <legend> element would only signal the grouping without communicating the 'what' the group is about. It is acceptable but it would be good to label the group.
Rather, I think the group role should not depend on the presence of aria-label or ariar-labelledby. To me, a listbox that contains only one set of options should not have a group. Instead, when a listbox contains two or more set of options, they should be groups and be labeled.

@afercia afercia force-pushed the fix/cover-placeholder-color-options-keyboard-accessibility branch from bb291c3 to e4c453e Compare February 10, 2025 10:33
@afercia
Copy link
Contributor Author

afercia commented Feb 10, 2025

I reverted the change to the OptionGroup ARIA role.
Apparently I was wrong. There is a listbox role. It's way up in the DOM hierarchy so that I couldn't see it.

@@ -514,6 +514,8 @@ function CoverEdit( {
value={ overlayColor.color }
onChange={ onSetOverlayColor }
clearable={ false }
asButtons
aria-label={ __( 'Background color' ) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: In the cover block, it is not called background color but overlay.
Even if there is no media selected.

.getByRole( 'listbox', {
name: 'Custom color picker.',
.getByRole( 'group', {
name: 'Background color',
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, this background color should be overlay.

@carolinan
Copy link
Contributor

I am not able to do the code review, but I have confirmed that I can use the tab key or the right and left arrow keys to navigate inside the cover block placeholder to select different colors or media options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Cover Affects the Cover Block - used to display content laid over a background image [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Package] Block library /packages/block-library [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
Status: 🔎 Needs Review
Development

Successfully merging this pull request may close these issues.

Cover placeholder: color palette not fully operable with keyboard
4 participants