Skip to content

Commit 7248a06

Browse files
Improve screen reader accessibility
Ramp task: https://ramp.accessibleweb.com/app/websites/83ca83c6265c/tasks/2787246f7ae2 This allows following WAI-ARIA best practices for combobox elements - https://www.w3.org/WAI/ARIA/apg/patterns/combobox/. Changes in this PR mostly correspond to the patw0929#405 Although most of the best practices to improve accessibility were implemented, the role="combobox" was not added neither the to the number input (because the input doesn't show/hide the dropdown), nor to the country code button. Looks like OSx VoiceOver announces everything correctly. Changes: - Add role="listbox" to the countries list - Refer the countries list by its Id in the country code button via aria-controls and aria-owns - Add role="option" to each country list option - Use aria-expanded on the country code button element - Refer focused option by its Id in the country code button element via aria-activedescendant to read focused option while navigating by keyboard - Add aria-selected="true" to the selected list option - Set aria-autocomplete="none" to the country code button element since displayed options do not depend on the number input value - Allow passing arbitrary props to the country code button element via flagDropdownProps prop (for example, "aria-labelledby" property can be passed) Updates dist files (built with node 7)
1 parent ad6350d commit 7248a06

File tree

9 files changed

+45
-10
lines changed

9 files changed

+45
-10
lines changed

dist/main.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/example.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/main.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/CountryList.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class CountryList extends Component {
6666
};
6767
const countryClass = classNames(countryClassObj);
6868
const keyPrefix = isPreferred ? 'pref-' : '';
69+
const isSelected = this.props.selectedCountryCode === country.iso2;
6970

7071
return (
7172
<li
@@ -75,6 +76,10 @@ class CountryList extends Component {
7576
data-country-code={ country.iso2 }
7677
onMouseOver={ this.props.isMobile ? null : this.handleMouseOver }
7778
onClick={ partial(this.setFlag, country.iso2) }
79+
role="option"
80+
aria-selected={ isSelected }
81+
id={ this.props.getItemId(actualIndex) }
82+
tabIndex="-1"
7883
>
7984
<div
8085
ref={ (selectedFlag) => { this.selectedFlag = selectedFlag; } }
@@ -125,6 +130,8 @@ class CountryList extends Component {
125130
<ul
126131
ref={ (listElement) => { this.listElement = listElement; } }
127132
className={ className }
133+
id={ this.props.countryListId }
134+
role="listbox"
128135
>
129136
{ preferredOptions }
130137
{ divider }
@@ -144,6 +151,9 @@ CountryList.propTypes = {
144151
changeHighlightCountry: PropTypes.func,
145152
showDropdown: PropTypes.bool,
146153
isMobile: PropTypes.bool,
154+
selectedCountryCode: PropTypes.string.isRequired,
155+
countryListId: PropTypes.string.isRequired,
156+
getItemId: PropTypes.func.isRequired,
147157
};
148158

149159
export default CountryList;

src/components/FlagDropDown.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import CountryList from './CountryList';
55
import RootModal from './RootModal';
66

77
class FlagDropDown extends Component {
8+
getItemId = (id) => `intl-tel-item-${this.props.uniqueId}-${id}`
9+
810
render() {
911
const flagClassObj = {
1012
'iti-flag': true,
@@ -35,6 +37,8 @@ class FlagDropDown extends Component {
3537

3638
let genCountryList = () => '';
3739

40+
const countryListId = `intl-tel-countries-list-${this.props.uniqueId}`;
41+
3842
if (this.props.dropdownContainer) {
3943
if (this.props.showDropdown) {
4044
genCountryList = () =>
@@ -51,6 +55,9 @@ class FlagDropDown extends Component {
5155
preferredCountries={ this.props.preferredCountries }
5256
highlightedCountry={ this.props.highlightedCountry }
5357
changeHighlightCountry={ this.props.changeHighlightCountry }
58+
selectedCountryCode={ this.props.countryCode }
59+
countryListId={ countryListId }
60+
getItemId={ this.getItemId }
5461
/>
5562
</RootModal>;
5663
}
@@ -68,6 +75,9 @@ class FlagDropDown extends Component {
6875
preferredCountries={ this.props.preferredCountries }
6976
highlightedCountry={ this.props.highlightedCountry }
7077
changeHighlightCountry={ this.props.changeHighlightCountry }
78+
selectedCountryCode={ this.props.countryCode }
79+
countryListId={ countryListId }
80+
getItemId={ this.getItemId }
7181
/>;
7282
}
7383

@@ -77,11 +87,17 @@ class FlagDropDown extends Component {
7787
className="flag-container"
7888
>
7989
<div
90+
{ ...this.props.flagDropdownProps }
8091
className="selected-flag"
8192
tabIndex={ this.props.allowDropdown ? '0' : '' }
8293
onClick={ this.props.clickSelectedFlag }
8394
onKeyDown={ this.props.handleSelectedFlagKeydown }
8495
title={ this.props.titleTip }
96+
aria-controls={ countryListId }
97+
aria-owns={ countryListId }
98+
aria-autocomplete="none"
99+
aria-activedescendant={ this.getItemId(this.props.highlightedCountry) }
100+
aria-expanded={ this.props.showDropdown }
85101
>
86102
<div className={ flagClass } />
87103
{ genSelectedDialCode() }
@@ -112,6 +128,8 @@ FlagDropDown.propTypes = {
112128
changeHighlightCountry: PropTypes.func,
113129
titleTip: PropTypes.string,
114130
refCallback: PropTypes.func.isRequired,
131+
uniqueId: PropTypes.string.isRequired,
132+
flagDropdownProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
115133
};
116134

117135
export default FlagDropDown;

src/components/IntlTelInputApp.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ class IntlTelInputApp extends Component {
100100
this.handleInputChange = this.handleInputChange.bind(this);
101101
this.changeHighlightCountry = this.changeHighlightCountry.bind(this);
102102
this.formatNumber = this.formatNumber.bind(this);
103+
// using a unique Id not to interfere with other instances of the tel input
104+
this.uniqueId = Math.random().toString(36).substring(2);
103105
}
104106

105107
componentDidMount() {
@@ -1151,6 +1153,8 @@ class IntlTelInputApp extends Component {
11511153
preferredCountries={ this.preferredCountries }
11521154
highlightedCountry={ this.state.highlightedCountry }
11531155
titleTip={ titleTip }
1156+
flagDropdownProps={ this.props.flagDropdownProps }
1157+
uniqueId={ this.uniqueId }
11541158
/>
11551159
<TelInput
11561160
refCallback={ this.setTelRef }
@@ -1210,6 +1214,7 @@ IntlTelInputApp.propTypes = {
12101214
style: StylePropTypes,
12111215
useMobileFullscreenDropdown: PropTypes.bool,
12121216
telInputProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
1217+
flagDropdownProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
12131218
format: PropTypes.bool,
12141219
};
12151220

@@ -1261,6 +1266,8 @@ IntlTelInputApp.defaultProps = {
12611266
autoComplete: 'off',
12621267
// pass through arbitrary props to the tel input element
12631268
telInputProps: {},
1269+
// pass through arbitrary props to the flag dropdown element
1270+
flagDropdownProps: {},
12641271
// always format the number
12651272
format: false,
12661273
};

src/styles/intlTelInput.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ $mobilePopupMargin: 30px;
7777
// full height
7878
top: 0;
7979
bottom: 0;
80-
right: 0;
80+
left: 0;
8181
// prevent the highlighted child from overlapping the input border
8282
padding: $borderWidth;
8383
}

0 commit comments

Comments
 (0)