[A11Y] Add aria-label to dropdown toggles (#2668)

Implement custom accessible dropdown toggle labels for forum components

Making the a11y label more specific to the specific action it performs is critical for good UX with assistive technologies.
This commit is contained in:
David Wheatley 2021-03-16 14:50:36 +00:00 committed by GitHub
parent 0e6a60bd5b
commit 0d139e6133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 38 additions and 2 deletions

View File

@ -13,6 +13,7 @@ import listItems from '../helpers/listItems';
* - `icon` The name of an icon to show in the dropdown toggle button.
* - `caretIcon` The name of an icon to show on the right of the button.
* - `label` The label of the dropdown toggle button. Defaults to 'Controls'.
* - `accessibleToggleLabel` The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'.
* - `onhide`
* - `onshow`
*
@ -25,6 +26,7 @@ export default class Dropdown extends Component {
attrs.menuClassName = attrs.menuClassName || '';
attrs.label = attrs.label || '';
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down';
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.lib.dropdown.toggle_dropdown_accessible_label');
}
oninit(vnode) {
@ -92,7 +94,13 @@ export default class Dropdown extends Component {
*/
getButton(children) {
return (
<button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}>
<button
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
onclick={this.attrs.onclick}
>
{this.getButtonContent(children)}
</button>
);

View File

@ -24,7 +24,12 @@ export default class SplitDropdown extends Dropdown {
return [
Button.component(buttonAttrs, firstChild.children),
<button className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName} data-toggle="dropdown">
<button
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
>
{icon(this.attrs.icon, { className: 'Button-icon' })}
{icon('fas fa-caret-down', { className: 'Button-caret' })}
</button>,

View File

@ -87,6 +87,7 @@ export default class DiscussionListItem extends Component {
icon: 'fas fa-ellipsis-v',
className: 'DiscussionListItem-controls',
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
},
controls
)

View File

@ -189,6 +189,7 @@ export default class DiscussionPage extends Page {
icon: 'fas fa-ellipsis-v',
className: 'App-primaryControl',
buttonClassName: 'Button--primary',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
},
DiscussionControls.controls(this.discussion, this).toArray()
)

View File

@ -57,6 +57,7 @@ export default class HeaderSecondary extends Component {
SelectDropdown.component(
{
buttonClassName: 'Button Button--link',
accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'),
},
locales
),

View File

@ -172,6 +172,7 @@ export default class IndexPage extends Page {
{
buttonClassName: 'Button',
className: 'App-titleControl',
accessibleToggleLabel: app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label'),
},
this.navItems(this).toArray()
)
@ -227,6 +228,7 @@ export default class IndexPage extends Page {
{
buttonClassName: 'Button',
label: sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0],
accessibleToggleLabel: app.translator.trans('core.forum.index_sort.toggle_dropdown_accessible_label'),
},
Object.keys(sortOptions).map((value) => {
const label = sortOptions[value];

View File

@ -9,6 +9,8 @@ export default class NotificationsDropdown extends Dropdown {
attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right';
attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip');
attrs.icon = attrs.icon || 'fas fa-bell';
// For best a11y support, both `title` and `aria-label` should be used
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.forum.notifications.toggle_dropdown_accessible_label');
super.initAttrs(attrs);
}

View File

@ -61,6 +61,7 @@ export default class Post extends Component {
icon="fas fa-ellipsis-h"
onshow={() => this.$('.Post-actions').addClass('open')}
onhide={() => this.$('.Post-actions').removeClass('open')}
accessibleToggleLabel={app.translator.trans('core.forum.post_controls.toggle_dropdown_accessible_label')}
>
{controls}
</Dropdown>

View File

@ -17,6 +17,8 @@ export default class SessionDropdown extends Dropdown {
attrs.className = 'SessionDropdown';
attrs.buttonClassName = 'Button Button--user Button--flat';
attrs.menuClassName = 'Dropdown-menu--right';
attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');
}
view(vnode) {

View File

@ -40,6 +40,7 @@ export default class UserCard extends Component {
menuClassName: 'Dropdown-menu--right',
buttonClassName: this.attrs.controlsButtonClassName,
label: app.translator.trans('core.forum.user_controls.button'),
accessibleToggleLabel: app.translator.trans('core.forum.user_controls.toggle_dropdown_accessible_label'),
icon: 'fas fa-ellipsis-v',
},
controls

View File

@ -278,6 +278,7 @@ core:
rename_button: => core.ref.rename
reply_button: => core.ref.reply
restore_button: => core.ref.restore
toggle_dropdown_accessible_label: Toggle discussion actions dropdown menu
# These translations are used in the discussion list.
discussion_list:
@ -316,10 +317,12 @@ core:
header:
admin_button: Administration
back_to_index_tooltip: Back to Discussion List
locale_dropdown_accessible_label: Change forum locale
log_in_link: => core.ref.log_in
log_out_button: => core.ref.log_out
profile_button: Profile
search_placeholder: Search Forum
session_dropdown_accessible_label: Toggle session options dropdown menu
settings_button: => core.ref.settings
sign_up_link: => core.ref.sign_up
@ -332,6 +335,7 @@ core:
meta_title_text: => core.ref.all_discussions
refresh_tooltip: Refresh
start_discussion_button: => core.ref.start_a_discussion
toggle_sidenav_dropdown_accessible_label: Toggle navigation dropdown menu
# These translations are used by the sorting control above the discussion list.
index_sort:
@ -339,6 +343,7 @@ core:
newest_button: Newest
oldest_button: Oldest
relevance_button: Relevance
toggle_dropdown_accessible_label: Change discussion list sorting
top_button: Top
# These translations are used in the Log In modal dialog.
@ -359,6 +364,7 @@ core:
mark_all_as_read_tooltip: => core.ref.mark_all_as_read
mark_as_read_tooltip: Mark as Read
title: => core.ref.notifications
toggle_dropdown_accessible_label: View notifications
tooltip: => core.ref.notifications
# These translations are used by tooltips displayed for individual posts.
@ -375,6 +381,7 @@ core:
edit_button: => core.ref.edit
hide_confirmation: "Are you sure you want to delete this post?"
restore_button: => core.ref.restore
toggle_dropdown_accessible_label: Toggle post controls dropdown menu
# These translations are used in the scrubber to the right of the post stream.
post_scrubber:
@ -448,6 +455,7 @@ core:
delete_error_message: "Deletion of user <i>{username} ({email})</i> failed"
delete_success_message: "User <i>{username} ({email})</i> was deleted"
edit_button: => core.ref.edit
toggle_dropdown_accessible_label: Toggle user controls dropdown menu
# These translations are used in the alert that is shown when a new user has not confirmed their email address.
user_email_confirmation:
@ -462,6 +470,10 @@ core:
badge:
hidden_tooltip: Hidden
# These translations are used in the dropdown component.
dropdown:
toggle_dropdown_accessible_label: Toggle dropdown menu
# These translations are displayed as error messages.
error:
dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"