Skip to content

Commit

Permalink
[A11Y] Add aria-label to dropdown toggles (#2668)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
davwheat authored Mar 16, 2021
1 parent 0e6a60b commit 0d139e6
Show file tree
Hide file tree
Showing 11 changed files with 38 additions and 2 deletions.
10 changes: 9 additions & 1 deletion js/src/common/components/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
*
Expand All @@ -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) {
Expand Down Expand Up @@ -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>
);
Expand Down
7 changes: 6 additions & 1 deletion js/src/common/components/SplitDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/components/DiscussionListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/components/DiscussionPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/components/HeaderSecondary.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
Expand Down
2 changes: 2 additions & 0 deletions js/src/forum/components/IndexPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand Down Expand Up @@ -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];
Expand Down
2 changes: 2 additions & 0 deletions js/src/forum/components/NotificationsDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/components/Post.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions js/src/forum/components/SessionDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions js/src/forum/components/UserCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions locale/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -332,13 +335,15 @@ 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:
latest_button: Latest
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.
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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}"
Expand Down

0 comments on commit 0d139e6

Please sign in to comment.