Skip to content
Permalink
Browse files

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
  • Loading branch information...
Marc Mulcahy authored and facebook-github-bot committed Apr 25, 2019
1 parent 421ffb0 commit 1aeac1c62528004d994200664368dc85fba1795d
@@ -22,7 +22,32 @@ export type AccessibilityRole =
| 'adjustable'
| 'imagebutton'
| 'header'
| 'summary';
| 'summary'
| 'alert'
| 'checkbox'
| 'combobox'
| 'menu'
| 'menubar'
| 'menuitem'
| 'progressbar'
| 'radio'
| 'radiogroup'
| 'scrollbar'
| 'spinbutton'
| 'switch'
| 'tab'
| 'tablist'
| 'timer'
| 'toolbar';

// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
export type AccessibilityStates = $ReadOnlyArray<'disabled' | 'selected'>;
export type AccessibilityStates = $ReadOnlyArray<
| 'disabled'
| 'selected'
| 'checked'
| 'unchecked'
| 'busy'
| 'expanded'
| 'collapsed'
| 'hasPopup',
>;
@@ -24,7 +24,32 @@ module.exports = {
'imagebutton',
'header',
'summary',
'alert',
'checkbox',
'combobox',
'menu',
'menubar',
'menuitem',
'progressbar',
'radio',
'radiogroup',
'scrollbar',
'spinbutton',
'switch',
'tab',
'tablist',
'timer',
'toolbar',
],
// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
DeprecatedAccessibilityStates: ['selected', 'disabled'],
DeprecatedAccessibilityStates: [
'selected',
'disabled',
'checked',
'unchecked',
'busy',
'expanded',
'collapsed',
'hasPopup',
],
};
@@ -12,10 +12,14 @@
const React = require('react');
const {
AccessibilityInfo,
Button,
Text,
View,
TouchableOpacity,
Alert,
UIManager,
findNodeHandle,
Platform,
} = require('react-native');

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

class CheckboxExample extends React.Component {
state = {
checkboxState: 'checked',
};

_onCheckboxPress = () => {
const checkboxState =
this.state.checkboxState === 'checked' ? 'unchecked' : 'checked';

this.setState({
checkboxState: checkboxState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onCheckboxPress}
accessibilityLabel="element 2"
accessibilityRole="checkbox"
accessibilityStates={[this.state.checkboxState]}
accessibilityHint="click me to change state">
<Text>Checkbox example</Text>
</TouchableOpacity>
);
}
}

class SwitchExample extends React.Component {
state = {
switchState: 'checked',
};

_onSwitchToggle = () => {
const switchState =
this.state.switchState === 'checked' ? 'unchecked' : 'checked';

this.setState({
switchState: switchState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onSwitchToggle}
accessibilityLabel="element 12"
accessibilityRole="switch"
accessibilityStates={[this.state.switchState]}
accessible={true}>
<Text>Switch example</Text>
</TouchableOpacity>
);
}
}

class SelectionExample extends React.Component {
constructor(props) {
super(props);
this.selectableElement = React.createRef();
}

state = {
isSelected: true,
isEnabled: false,
};

render() {
let accessibilityStates = [];
let accessibilityHint = 'click me to select';
if (this.state.isSelected) {
accessibilityStates.push('selected');
accessibilityHint = 'click me to unselect';
}
if (!this.state.isEnabled) {
accessibilityStates.push('disabled');
accessibilityHint = 'use the button on the right to enable selection';
}
let buttonTitle = this.state.isEnabled
? 'Disable selection'
: 'Enable selection';

return (
<View style={{flex: 1, flexDirection: 'row'}}>
<TouchableOpacity
ref={this.selectableElement}
accessible={true}
onPress={() => {
this.setState({
isSelected: !this.state.isSelected,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this.selectableElement.current),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
}}
accessibilityLabel="element 19"
accessibilityStates={accessibilityStates}
accessibilityHint={accessibilityHint}>
<Text>Selectable element example</Text>
</TouchableOpacity>
<Button
onPress={() => {
this.setState({
isEnabled: !this.state.isEnabled,
});
}}
title={buttonTitle}
/>
</View>
);
}
}

class ExpandableElementExample extends React.Component {
state = {
expandState: 'collapsed',
};

_onElementPress = () => {
const expandState =
this.state.expandState === 'collapsed' ? 'expanded' : 'collapsed';

this.setState({
expandState: expandState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onElementPress}
accessibilityLabel="element 18"
accessibilityStates={[this.state.expandState]}
accessibilityHint="click me to change state">
<Text>Expandable element example</Text>
</TouchableOpacity>
);
}
}

class AccessibilityRoleAndStateExample extends React.Component<{}> {
render() {
return (
<View>
<View
accessibilityLabel="element 1"
accessibilityRole="alert"
accessible={true}>
<Text>Alert example</Text>
</View>
<CheckboxExample />
<View
accessibilityLabel="element 3"
accessibilityRole="combobox"
accessible={true}>
<Text>Combobox example</Text>
</View>
<View
accessibilityLabel="element 4"
accessibilityRole="menu"
accessible={true}>
<Text>Menu example</Text>
</View>
<View
accessibilityLabel="element 5"
accessibilityRole="menubar"
accessible={true}>
<Text>Menu bar example</Text>
</View>
<View
accessibilityLabel="element 6"
accessibilityRole="menuitem"
accessible={true}>
<Text>Menu item example</Text>
</View>
<View
accessibilityLabel="element 7"
accessibilityRole="progressbar"
accessible={true}>
<Text>Progress bar example</Text>
</View>
<View
accessibilityLabel="element 8"
accessibilityRole="radio"
accessible={true}>
<Text>Radio button example</Text>
</View>
<View
accessibilityLabel="element 9"
accessibilityRole="radiogroup"
accessible={true}>
<Text>Radio group example</Text>
</View>
<View
accessibilityLabel="element 10"
accessibilityRole="scrollbar"
accessible={true}>
<Text>Scrollbar example</Text>
</View>
<View
accessibilityLabel="element 11"
accessibilityRole="spinbutton"
accessible={true}>
<Text>Spin button example</Text>
</View>
<SwitchExample />
<View
accessibilityLabel="element 13"
accessibilityRole="tab"
accessible={true}>
<Text>Tab example</Text>
</View>
<View
accessibilityLabel="element 14"
accessibilityRole="tablist"
accessible={true}>
<Text>Tab list example</Text>
</View>
<View
accessibilityLabel="element 15"
accessibilityRole="timer"
accessible={true}>
<Text>Timer example</Text>
</View>
<View
accessibilityLabel="element 16"
accessibilityRole="toolbar"
accessible={true}>
<Text>Toolbar example</Text>
</View>
<View
accessibilityLabel="element 17"
accessibilityStates={['busy']}
accessible={true}>
<Text>State busy example</Text>
</View>
<ExpandableElementExample />
<SelectionExample />
</View>
);
}
}

class ScreenReaderStatusExample extends React.Component<{}> {
state = {
screenReaderEnabled: false,
@@ -189,6 +461,12 @@ exports.examples = [
return <AccessibilityExample />;
},
},
{
title: 'New accessibility roles and states',
render(): React.Element<typeof AccessibilityRoleAndStateExamples> {
return <AccessibilityRoleAndStateExample />;
},
},
{
title: 'Check if the screen reader is enabled',
render(): React.Element<typeof ScreenReaderStatusExample> {
@@ -12,6 +12,8 @@
#import <React/RCTPointerEvents.h>
#import <React/RCTView.h>

extern const UIAccessibilityTraits SwitchAccessibilityTrait;

@protocol RCTAutoInsetsProtocol;

@class RCTView;
@@ -30,6 +32,8 @@
* Accessibility properties
*/
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions;
@property (nonatomic, copy) NSString *accessibilityRole;
@property (nonatomic, copy) NSArray <NSString *> *accessibilityStates;

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

0 comments on commit 1aeac1c

Please sign in to comment.
You can’t perform that action at this time.