Skip to content

Commit 1aeac1c

Browse files
Marc Mulcahyfacebook-github-bot
Marc Mulcahy
authored andcommitted
Additional Accessibility Roles and States (#24095)
Summary: Assistive technologies use the accessibility role of a component to tell the disabled user what the component is, and provide hints about how to use it. Many important roles do not have analog AccessibilityTraits on iOS. This PR adds many critical roles, such as editabletext, checkbox, menu, and switch to name a few. Accessibility states are used to convey the current state of a component. This PR adds several critical states such as checked, unchecked, on and off. [general] [change] - Adds critical accessibility roles and states. Pull Request resolved: #24095 Differential Revision: D15079245 Pulled By: cpojer fbshipit-source-id: 941b30eb8f5d565597e5ea3a04687d9809cbe372
1 parent 421ffb0 commit 1aeac1c

File tree

10 files changed

+743
-120
lines changed

10 files changed

+743
-120
lines changed

Libraries/Components/View/ViewAccessibility.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,32 @@ export type AccessibilityRole =
2222
| 'adjustable'
2323
| 'imagebutton'
2424
| 'header'
25-
| 'summary';
25+
| 'summary'
26+
| 'alert'
27+
| 'checkbox'
28+
| 'combobox'
29+
| 'menu'
30+
| 'menubar'
31+
| 'menuitem'
32+
| 'progressbar'
33+
| 'radio'
34+
| 'radiogroup'
35+
| 'scrollbar'
36+
| 'spinbutton'
37+
| 'switch'
38+
| 'tab'
39+
| 'tablist'
40+
| 'timer'
41+
| 'toolbar';
2642

2743
// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
28-
export type AccessibilityStates = $ReadOnlyArray<'disabled' | 'selected'>;
44+
export type AccessibilityStates = $ReadOnlyArray<
45+
| 'disabled'
46+
| 'selected'
47+
| 'checked'
48+
| 'unchecked'
49+
| 'busy'
50+
| 'expanded'
51+
| 'collapsed'
52+
| 'hasPopup',
53+
>;

Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,32 @@ module.exports = {
2424
'imagebutton',
2525
'header',
2626
'summary',
27+
'alert',
28+
'checkbox',
29+
'combobox',
30+
'menu',
31+
'menubar',
32+
'menuitem',
33+
'progressbar',
34+
'radio',
35+
'radiogroup',
36+
'scrollbar',
37+
'spinbutton',
38+
'switch',
39+
'tab',
40+
'tablist',
41+
'timer',
42+
'toolbar',
2743
],
2844
// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
29-
DeprecatedAccessibilityStates: ['selected', 'disabled'],
45+
DeprecatedAccessibilityStates: [
46+
'selected',
47+
'disabled',
48+
'checked',
49+
'unchecked',
50+
'busy',
51+
'expanded',
52+
'collapsed',
53+
'hasPopup',
54+
],
3055
};

RNTester/js/AccessibilityExample.js

+278
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
const React = require('react');
1313
const {
1414
AccessibilityInfo,
15+
Button,
1516
Text,
1617
View,
1718
TouchableOpacity,
1819
Alert,
20+
UIManager,
21+
findNodeHandle,
22+
Platform,
1923
} = require('react-native');
2024

2125
const RNTesterBlock = require('./RNTesterBlock');
@@ -138,6 +142,274 @@ class AccessibilityExample extends React.Component {
138142
}
139143
}
140144

145+
class CheckboxExample extends React.Component {
146+
state = {
147+
checkboxState: 'checked',
148+
};
149+
150+
_onCheckboxPress = () => {
151+
const checkboxState =
152+
this.state.checkboxState === 'checked' ? 'unchecked' : 'checked';
153+
154+
this.setState({
155+
checkboxState: checkboxState,
156+
});
157+
158+
if (Platform.OS === 'android') {
159+
UIManager.sendAccessibilityEvent(
160+
findNodeHandle(this),
161+
UIManager.AccessibilityEventTypes.typeViewClicked,
162+
);
163+
}
164+
};
165+
166+
render() {
167+
return (
168+
<TouchableOpacity
169+
onPress={this._onCheckboxPress}
170+
accessibilityLabel="element 2"
171+
accessibilityRole="checkbox"
172+
accessibilityStates={[this.state.checkboxState]}
173+
accessibilityHint="click me to change state">
174+
<Text>Checkbox example</Text>
175+
</TouchableOpacity>
176+
);
177+
}
178+
}
179+
180+
class SwitchExample extends React.Component {
181+
state = {
182+
switchState: 'checked',
183+
};
184+
185+
_onSwitchToggle = () => {
186+
const switchState =
187+
this.state.switchState === 'checked' ? 'unchecked' : 'checked';
188+
189+
this.setState({
190+
switchState: switchState,
191+
});
192+
193+
if (Platform.OS === 'android') {
194+
UIManager.sendAccessibilityEvent(
195+
findNodeHandle(this),
196+
UIManager.AccessibilityEventTypes.typeViewClicked,
197+
);
198+
}
199+
};
200+
201+
render() {
202+
return (
203+
<TouchableOpacity
204+
onPress={this._onSwitchToggle}
205+
accessibilityLabel="element 12"
206+
accessibilityRole="switch"
207+
accessibilityStates={[this.state.switchState]}
208+
accessible={true}>
209+
<Text>Switch example</Text>
210+
</TouchableOpacity>
211+
);
212+
}
213+
}
214+
215+
class SelectionExample extends React.Component {
216+
constructor(props) {
217+
super(props);
218+
this.selectableElement = React.createRef();
219+
}
220+
221+
state = {
222+
isSelected: true,
223+
isEnabled: false,
224+
};
225+
226+
render() {
227+
let accessibilityStates = [];
228+
let accessibilityHint = 'click me to select';
229+
if (this.state.isSelected) {
230+
accessibilityStates.push('selected');
231+
accessibilityHint = 'click me to unselect';
232+
}
233+
if (!this.state.isEnabled) {
234+
accessibilityStates.push('disabled');
235+
accessibilityHint = 'use the button on the right to enable selection';
236+
}
237+
let buttonTitle = this.state.isEnabled
238+
? 'Disable selection'
239+
: 'Enable selection';
240+
241+
return (
242+
<View style={{flex: 1, flexDirection: 'row'}}>
243+
<TouchableOpacity
244+
ref={this.selectableElement}
245+
accessible={true}
246+
onPress={() => {
247+
this.setState({
248+
isSelected: !this.state.isSelected,
249+
});
250+
251+
if (Platform.OS === 'android') {
252+
UIManager.sendAccessibilityEvent(
253+
findNodeHandle(this.selectableElement.current),
254+
UIManager.AccessibilityEventTypes.typeViewClicked,
255+
);
256+
}
257+
}}
258+
accessibilityLabel="element 19"
259+
accessibilityStates={accessibilityStates}
260+
accessibilityHint={accessibilityHint}>
261+
<Text>Selectable element example</Text>
262+
</TouchableOpacity>
263+
<Button
264+
onPress={() => {
265+
this.setState({
266+
isEnabled: !this.state.isEnabled,
267+
});
268+
}}
269+
title={buttonTitle}
270+
/>
271+
</View>
272+
);
273+
}
274+
}
275+
276+
class ExpandableElementExample extends React.Component {
277+
state = {
278+
expandState: 'collapsed',
279+
};
280+
281+
_onElementPress = () => {
282+
const expandState =
283+
this.state.expandState === 'collapsed' ? 'expanded' : 'collapsed';
284+
285+
this.setState({
286+
expandState: expandState,
287+
});
288+
289+
if (Platform.OS === 'android') {
290+
UIManager.sendAccessibilityEvent(
291+
findNodeHandle(this),
292+
UIManager.AccessibilityEventTypes.typeViewClicked,
293+
);
294+
}
295+
};
296+
297+
render() {
298+
return (
299+
<TouchableOpacity
300+
onPress={this._onElementPress}
301+
accessibilityLabel="element 18"
302+
accessibilityStates={[this.state.expandState]}
303+
accessibilityHint="click me to change state">
304+
<Text>Expandable element example</Text>
305+
</TouchableOpacity>
306+
);
307+
}
308+
}
309+
310+
class AccessibilityRoleAndStateExample extends React.Component<{}> {
311+
render() {
312+
return (
313+
<View>
314+
<View
315+
accessibilityLabel="element 1"
316+
accessibilityRole="alert"
317+
accessible={true}>
318+
<Text>Alert example</Text>
319+
</View>
320+
<CheckboxExample />
321+
<View
322+
accessibilityLabel="element 3"
323+
accessibilityRole="combobox"
324+
accessible={true}>
325+
<Text>Combobox example</Text>
326+
</View>
327+
<View
328+
accessibilityLabel="element 4"
329+
accessibilityRole="menu"
330+
accessible={true}>
331+
<Text>Menu example</Text>
332+
</View>
333+
<View
334+
accessibilityLabel="element 5"
335+
accessibilityRole="menubar"
336+
accessible={true}>
337+
<Text>Menu bar example</Text>
338+
</View>
339+
<View
340+
accessibilityLabel="element 6"
341+
accessibilityRole="menuitem"
342+
accessible={true}>
343+
<Text>Menu item example</Text>
344+
</View>
345+
<View
346+
accessibilityLabel="element 7"
347+
accessibilityRole="progressbar"
348+
accessible={true}>
349+
<Text>Progress bar example</Text>
350+
</View>
351+
<View
352+
accessibilityLabel="element 8"
353+
accessibilityRole="radio"
354+
accessible={true}>
355+
<Text>Radio button example</Text>
356+
</View>
357+
<View
358+
accessibilityLabel="element 9"
359+
accessibilityRole="radiogroup"
360+
accessible={true}>
361+
<Text>Radio group example</Text>
362+
</View>
363+
<View
364+
accessibilityLabel="element 10"
365+
accessibilityRole="scrollbar"
366+
accessible={true}>
367+
<Text>Scrollbar example</Text>
368+
</View>
369+
<View
370+
accessibilityLabel="element 11"
371+
accessibilityRole="spinbutton"
372+
accessible={true}>
373+
<Text>Spin button example</Text>
374+
</View>
375+
<SwitchExample />
376+
<View
377+
accessibilityLabel="element 13"
378+
accessibilityRole="tab"
379+
accessible={true}>
380+
<Text>Tab example</Text>
381+
</View>
382+
<View
383+
accessibilityLabel="element 14"
384+
accessibilityRole="tablist"
385+
accessible={true}>
386+
<Text>Tab list example</Text>
387+
</View>
388+
<View
389+
accessibilityLabel="element 15"
390+
accessibilityRole="timer"
391+
accessible={true}>
392+
<Text>Timer example</Text>
393+
</View>
394+
<View
395+
accessibilityLabel="element 16"
396+
accessibilityRole="toolbar"
397+
accessible={true}>
398+
<Text>Toolbar example</Text>
399+
</View>
400+
<View
401+
accessibilityLabel="element 17"
402+
accessibilityStates={['busy']}
403+
accessible={true}>
404+
<Text>State busy example</Text>
405+
</View>
406+
<ExpandableElementExample />
407+
<SelectionExample />
408+
</View>
409+
);
410+
}
411+
}
412+
141413
class ScreenReaderStatusExample extends React.Component<{}> {
142414
state = {
143415
screenReaderEnabled: false,
@@ -189,6 +461,12 @@ exports.examples = [
189461
return <AccessibilityExample />;
190462
},
191463
},
464+
{
465+
title: 'New accessibility roles and states',
466+
render(): React.Element<typeof AccessibilityRoleAndStateExamples> {
467+
return <AccessibilityRoleAndStateExample />;
468+
},
469+
},
192470
{
193471
title: 'Check if the screen reader is enabled',
194472
render(): React.Element<typeof ScreenReaderStatusExample> {

React/Views/RCTView.h

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#import <React/RCTPointerEvents.h>
1313
#import <React/RCTView.h>
1414

15+
extern const UIAccessibilityTraits SwitchAccessibilityTrait;
16+
1517
@protocol RCTAutoInsetsProtocol;
1618

1719
@class RCTView;
@@ -30,6 +32,8 @@
3032
* Accessibility properties
3133
*/
3234
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions;
35+
@property (nonatomic, copy) NSString *accessibilityRole;
36+
@property (nonatomic, copy) NSArray <NSString *> *accessibilityStates;
3337

3438
/**
3539
* Used to control how touch events are processed.

0 commit comments

Comments
 (0)