/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {EventSubscription, GestureResponderEvent} from 'react-native';
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterText from '../../components/RNTesterText';
import checkImageSource from './check.png';
import mixedCheckboxImageSource from './mixed.png';
import uncheckImageSource from './uncheck.png';
import React, {createRef, useEffect, useRef, useState} from 'react';
import {
AccessibilityInfo,
Alert,
Button,
Image,
ImageBackground,
Platform,
Pressable,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableNativeFeedback,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native';
const styles = StyleSheet.create({
sectionContainer: {
rowGap: 20,
},
default: {
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
},
touchable: {
backgroundColor: 'blue',
borderColor: 'red',
borderWidth: 1,
borderRadius: 10,
padding: 10,
borderStyle: 'solid',
},
image: {
width: 20,
height: 20,
resizeMode: 'contain',
marginRight: 10,
},
disabledImage: {
width: 120,
height: 120,
},
containerAlignCenter: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
},
button: {
padding: 8,
borderWidth: 1,
borderColor: 'blue',
},
smallRedSquare: {
backgroundColor: 'red',
height: 40,
width: 40,
},
container: {
flex: 1,
},
ImageBackground: {
flex: 1,
justifyContent: 'center',
},
text: {
color: 'white',
fontSize: 20,
lineHeight: 84,
fontWeight: 'bold',
textAlign: 'center',
backgroundColor: '#000000c0',
},
scrollView: {
height: 50,
},
boxSize: {
width: 50,
height: 50,
},
smallBoxSize: {
width: 25,
height: 25,
},
bigBoxSize: {
width: 100,
height: 100,
},
link: {
color: 'blue',
},
});
class AccessibilityExample extends React.Component<{}> {
render(): React.Node {
return (
Text's accessibilityLabel is the raw text itself unless it is set
explicitly.
This text component's accessibilityLabel is set explicitly.
This is text one.
This is text two.
This is text one.
This is text two.
This is text one.
This is text two.
This view's children are hidden from the accessibility tree
{/* Android screen readers will say the accessibility hint instead of the text
since the view doesn't have a label. */}
This is text one.
This is text two.
This is text one.
This is text two.
This is a title.
This is a title.
Alert.alert('Link has been clicked!')}
accessibilityRole="link">
Click me
Alert.alert('Button has been pressed!')}
accessibilityRole="button">
Click me
Alert.alert('Button has been pressed!')}
accessibilityRole="button"
accessibilityState={{disabled: true}}
disabled={true}>
I am disabled. Clicking me will not trigger any action.
Alert.alert('Disabled Button has been pressed!')}
accessibilityLabel={'You are pressing Disabled TouchableOpacity'}
accessibilityState={{disabled: true}}>
I am disabled. Clicking me will not trigger any action.
This view is selected and disabled.
Accessible view with label, hint, role, and state
Accessible view with label, hint, role, and state
Mail Address
First Name
Enable Notifications
);
}
}
class AutomaticContentGrouping extends React.Component<{}> {
render(): React.Node {
return (
Text number 1 with a role
Text number 2
{
switch (event.nativeEvent.actionName) {
case 'cut':
Alert.alert('Alert', 'cut action success');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
case 'paste':
Alert.alert('Alert', 'paste action success');
break;
}
}}
accessibilityRole="button">
Text number 1
Text number 2
Text number 3
Text number 1
Text number 1
console.warn('onPress child')}
accessible={false}
accessibilityLabel="this is my label"
accessibilityRole="image"
accessibilityState={{disabled: true}}
accessibilityValue={{
text: 'this is the accessibility value',
}}>
Text number 3
Text number 2
Text number 3
Text number 4
console.warn('onPress child')}
accessible={true}
accessibilityRole="button">
);
}
}
class CheckboxExample extends React.Component<
{},
{
checkboxState: boolean | 'mixed',
},
> {
state: {checkboxState: boolean | 'mixed'} = {
checkboxState: true,
};
_onCheckboxPress = () => {
let checkboxState: boolean | string = false;
if (this.state.checkboxState === false) {
checkboxState = 'mixed';
} else if (this.state.checkboxState === 'mixed') {
checkboxState = true;
} else {
checkboxState = false;
}
this.setState({
checkboxState,
});
};
render(): React.Node {
return (
Checkbox example
);
}
}
class SwitchExample extends React.Component<
{},
{
switchState: boolean,
},
> {
state: {switchState: boolean} = {
switchState: true,
};
_onSwitchToggle = () => {
const switchState = !this.state.switchState;
this.setState({
switchState,
});
};
render(): React.Node {
return (
Switch example
);
}
}
class SelectionExample extends React.Component<
{},
{
isSelected: boolean,
isEnabled: boolean,
},
> {
constructor(props: {}) {
super(props);
this.selectableElement = createRef();
}
selectableElement: {
current: React.ElementRef | null,
};
state: {isEnabled: boolean, isSelected: boolean} = {
isSelected: true,
isEnabled: false,
};
render(): React.Node {
const {isSelected, isEnabled} = this.state;
let accessibilityHint = 'click me to select';
if (isSelected) {
accessibilityHint = 'click me to unselect';
}
if (!isEnabled) {
accessibilityHint = 'use the button on the right to enable selection';
}
const buttonTitle = isEnabled ? 'Disable selection' : 'Enable selection';
const touchableHint = ` (touching the TouchableOpacity will ${
isSelected ? 'disable' : 'enable'
} accessibilityState.selected)`;
return (
{
if (isEnabled) {
this.setState({
isSelected: !isSelected,
});
} else {
console.warn('selection is disabled, please enable selection.');
}
}}
accessibilityLabel="element 19"
accessibilityState={{
selected: isSelected,
disabled: !isEnabled,
}}
style={styles.touchable}
accessibilityHint={accessibilityHint}>
{`Selectable TouchableOpacity Example ${touchableHint}`}
);
}
}
class ExpandableElementExample extends React.Component<
{},
{
expandState: boolean,
},
> {
state: {expandState: boolean} = {
expandState: false,
};
_onElementPress = () => {
const expandState = !this.state.expandState;
this.setState({
expandState,
});
};
render(): React.Node {
return (
Expandable element example
);
}
}
class NestedCheckBox extends React.Component<
{},
{
checkbox1: boolean | 'mixed',
checkbox2: boolean | 'mixed',
checkbox3: boolean | 'mixed',
},
> {
state: {
checkbox1: boolean | 'mixed',
checkbox2: boolean | 'mixed',
checkbox3: boolean | 'mixed',
} = {
checkbox1: false,
checkbox2: false,
checkbox3: false,
};
_onPress1 = () => {
let checkbox1 = false;
if (this.state.checkbox1 === false) {
checkbox1 = true;
} else if (this.state.checkbox1 === 'mixed') {
checkbox1 = false;
} else {
checkbox1 = false;
}
setTimeout(() => {
this.setState({
checkbox1,
checkbox2: checkbox1,
checkbox3: checkbox1,
});
}, 2000);
};
_onPress2 = () => {
const checkbox2 = !this.state.checkbox2;
this.setState({
checkbox2,
checkbox1:
checkbox2 && this.state.checkbox3
? true
: checkbox2 || this.state.checkbox3
? 'mixed'
: false,
});
};
_onPress3 = () => {
const checkbox3 = !this.state.checkbox3;
this.setState({
checkbox3,
checkbox1:
this.state.checkbox2 && checkbox3
? true
: this.state.checkbox2 || checkbox3
? 'mixed'
: false,
});
};
render(): React.Node {
return (
Meat
Beef
Bacon
);
}
}
class AccessibilityRoleAndStateExample extends React.Component<{}> {
render(): React.Node {
const content = [
This is some text,
This is some text,
This is some text,
This is some text,
This is some text,
This is some text,
This is some text,
];
return (
{content}
{content}
{content}
Alert example
Combobox example
Menu example
Menu bar example
Menu item example
Progress bar example
Radio button example
Radio group example
Scrollbar example
Spin button example
Tab example
Tab list example
Timer example
Toolbar example
State busy example
Drop Down List example
Pager example
Toggle Button example
Viewgroup example
Webview example
Nested checkbox with delayed state change
);
}
}
class AccessibilityActionsExample extends React.Component<{}> {
render(): React.Node {
return (
{
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'View is clicked');
break;
}
}}>
Click me
{
switch (event.nativeEvent.actionName) {
case 'cut':
Alert.alert('Alert', 'cut action success');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
case 'paste':
Alert.alert('Alert', 'paste action success');
break;
}
}}>
This view supports many actions.
{
switch (event.nativeEvent.actionName) {
case 'increment':
Alert.alert('Alert', 'increment action success');
break;
case 'decrement':
Alert.alert('Alert', 'decrement action success');
break;
}
}}>
Slider
{
switch (event.nativeEvent.actionName) {
case 'cut':
Alert.alert('Alert', 'cut action success');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
case 'paste':
Alert.alert('Alert', 'paste action success');
break;
}
}}
onPress={() => Alert.alert('Button has been pressed!')}
accessibilityRole="button">
Click me
{
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'Activate accessibility action');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
}
}}>
Text
);
}
}
type FakeSliderExampleState = {
current: number,
textualValue: 'center' | 'left' | 'right',
};
class FakeSliderExample extends React.Component<{}, FakeSliderExampleState> {
state: FakeSliderExampleState = {
current: 50,
textualValue: 'center',
};
increment: () => void = () => {
let newValue = this.state.current + 2;
if (newValue > 100) {
newValue = 100;
}
this.setState({
current: newValue,
});
};
decrement: () => void = () => {
let newValue = this.state.current - 2;
if (newValue < 0) {
newValue = 0;
}
this.setState({
current: newValue,
});
};
render(): React.Node {
return (
{
switch (event.nativeEvent.actionName) {
case 'increment':
this.increment();
break;
case 'decrement':
this.decrement();
break;
}
}}
accessibilityValue={{
min: 0,
now: this.state.current,
max: 100,
}}>
Fake Slider {this.state.current}
{
switch (event.nativeEvent.actionName) {
case 'increment':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'right'});
} else if (this.state.textualValue === 'left') {
this.setState({textualValue: 'center'});
}
break;
case 'decrement':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'left'});
} else if (this.state.textualValue === 'right') {
this.setState({textualValue: 'center'});
}
break;
}
}}
accessibilityValue={{text: this.state.textualValue}}>
Equalizer {this.state.textualValue}
);
}
}
class FakeSliderExampleForAccessibilityValue extends React.Component<
{},
FakeSliderExampleState,
> {
state: FakeSliderExampleState = {
current: 50,
textualValue: 'center',
};
increment: () => void = () => {
let newValue = this.state.current + 2;
if (newValue > 100) {
newValue = 100;
}
this.setState({
current: newValue,
});
};
decrement: () => void = () => {
let newValue = this.state.current - 2;
if (newValue < 0) {
newValue = 0;
}
this.setState({
current: newValue,
});
};
render(): React.Node {
return (
{
switch (event.nativeEvent.actionName) {
case 'increment':
this.increment();
break;
case 'decrement':
this.decrement();
break;
}
}}
aria-valuemax={100}
aria-valuemin={0}
aria-valuetext={'slider aria value text'}
aria-valuenow={this.state.current}>
Fake Slider {this.state.current}
{
switch (event.nativeEvent.actionName) {
case 'increment':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'right'});
} else if (this.state.textualValue === 'left') {
this.setState({textualValue: 'center'});
}
break;
case 'decrement':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'left'});
} else if (this.state.textualValue === 'right') {
this.setState({textualValue: 'center'});
}
break;
}
}}
accessibilityValue={{text: this.state.textualValue}}>
Equalizer {this.state.textualValue}
);
}
}
class AnnounceForAccessibility extends React.Component<{}> {
_handleOnPress = (): TimeoutID =>
setTimeout(
() => AccessibilityInfo.announceForAccessibility('Announcement Test'),
1000,
);
_handleOnPressQueued = (): TimeoutID =>
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Queued Announcement Test',
{queue: true},
),
1000,
);
_handleOnPressQueueMultiple = () => {
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'First Queued Announcement Test',
{queue: true},
),
1000,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Second Queued Announcement Test',
{queue: true},
),
1100,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Third Queued Announcement Test',
{queue: true},
),
1200,
);
};
render(): React.Node {
return Platform.OS === 'ios' ? (
) : (
);
}
}
function SetAccessibilityFocusExample(props: {}): React.Node {
const myRef = useRef>(null);
const onPress = () => {
if (myRef && myRef.current) {
AccessibilityInfo.sendAccessibilityEvent(myRef.current, 'focus');
}
};
return (
{/* $FlowFixMe[incompatible-type] */}
SetAccessibilityFocus on native element. This should get focus after
clicking the button!
);
}
class EnabledExamples extends React.Component<{}> {
render(): React.Node {
return (
{Platform.OS === 'ios' ? (
<>
>
) : null}
{Platform.OS === 'android' ? (
) : null}
);
}
}
class ImportantForAccessibilityExamples extends React.Component<{}> {
render(): React.Node {
return (
not accessible
accessible
);
}
}
class EnabledExample extends React.Component<
{
eventListener:
| 'reduceMotionChanged'
| 'boldTextChanged'
| 'grayscaleChanged'
| 'invertColorsChanged'
| 'reduceTransparencyChanged'
| 'reduceMotionChanged'
| 'screenReaderChanged'
| 'accessibilityServiceChanged',
test: string,
},
{
isEnabled: boolean,
},
> {
state: {isEnabled: boolean} = {
isEnabled: false,
};
_subscription: EventSubscription;
componentDidMount(): null | Promise {
this._subscription = AccessibilityInfo.addEventListener(
this.props.eventListener,
this._handleToggled,
);
switch (this.props.eventListener) {
case 'reduceMotionChanged':
return AccessibilityInfo.isReduceMotionEnabled().then(state => {
this.setState({isEnabled: state});
});
case 'accessibilityServiceChanged':
return AccessibilityInfo.isAccessibilityServiceEnabled().then(state => {
this.setState({isEnabled: state});
});
default:
return null;
}
}
componentWillUnmount() {
this._subscription?.remove();
}
_handleToggled = (isEnabled: void | GestureResponderEvent | boolean) => {
if (!this.state.isEnabled) {
this.setState({isEnabled: true});
} else {
this.setState({isEnabled: false});
}
};
render(): React.Node {
return (
The {this.props.test} is{' '}
{this.state.isEnabled ? 'enabled' : 'disabled'}
);
}
}
class DisplayOptionsStatusExample extends React.Component<{}> {
render(): React.Node {
const isAndroid = Platform.OS === 'android';
return (
{isAndroid ? null : (
<>
>
)}
);
}
}
function DisplayOptionStatusExample({
optionName,
optionChecker,
notification,
}: {
notification: string,
optionChecker: () => Promise,
optionName: string,
}) {
const [statusEnabled, setStatusEnabled] = useState(false);
useEffect(() => {
const listener = AccessibilityInfo.addEventListener(
// $FlowFixMe[prop-missing]
// $FlowFixMe[invalid-computed-prop]
notification,
setStatusEnabled,
);
// $FlowFixMe[unused-promise]
optionChecker().then(isEnabled => {
setStatusEnabled(isEnabled);
});
return function cleanup() {
listener.remove();
};
}, [optionChecker, notification]);
return (
{optionName}
{' is '}
{statusEnabled ? 'enabled' : 'disabled'}.
);
}
function AccessibilityExpandedExample(): React.Node {
const [expand, setExpanded] = useState(false);
const expandAction = {name: 'expand'};
const collapseAction = {name: 'collapse'};
return (
The following component announces expanded/collapsed state correctly
Announcing expanded/collapse and the visible text.
setExpanded(!expand)}
accessibilityState={{expanded: expand}}>
Click me to change state
Clicking me does not change state
);
}
function AccessibilityTextInputWithArialabelledByAttributeExample(): React.Node {
return (
Phone Number
);
}
function AccessibilityOrderExample(): React.Node {
return (
<>
Basic accessibility order: accessibilityOrder=['b', 'c', 'a']
Accessibility order disables components not in the order:
accessibilityOrder=['b', 'c', 'a']
Not in the order :P
I am a pressable
Accessibility order won't enable non-accessible components in the order:
accessibilityOrder=['b', 'c', 'a']
Accessibility order can reference containers: accessibilityOrder=['b',
'c', 'a']
Accessibility order can reference other accessibilityOrders:
accessibilityOrder=['b', 'c', 'a']. a's accessibilityOrder=[4, 2, 1, 3]
Accessibility order cannot order the root, but it can still be
accessible: accessibilityOrder=['b', 'c', 'root', 'a'].
Accessibility order can reference parents and their decendants in any
order: accessibilityOrder=[child 2, child 1, child 2.3, child 3.2, child
4, child 2.4, parent, child 2.1]
Accessibility order will still co-opt labels even if they are not in the
order: accessibilityOrder=[b, c, a]
I am co-opted by my parent!!
I won't get co-opted because my parent has a label
Accessibility order let's you focus links, but the text needs to be
included: accessibilityOrder=[c, a, b, text]
I am a{' '}
link!
I am a{' '}
non-discoverable link!
>
);
}
function handleLinkPress(linkText: string): void {
Alert.alert('Link Clicked', `You clicked on the ${linkText} link!`);
}
function TextLinkExample(): React.Node {
return (
We can focus{' '}
handleLinkPress('first')}
style={styles.link}>
links
{' '}
in text, even if there are{' '}
handleLinkPress('second')}
style={styles.link}>
multiple of them!
handleLinkPress('thrid')}
style={styles.link}>
We can also focus text that are entirly links!
);
}
function LabelCooptingExample(): React.Node {
return (
This View is accessible and it will coopt this text. This text is not
accessible because it got coopted!
I am some text that will get coopted
I am also some text that will get coopted
This View is accessible and it will coopt this text. This text is not
accessible because it got coopted! But it's{' '}
handleLinkPress('first')}
style={styles.link}>
links
{' '}
are!
);
}
function AriaHiddenExample(): React.Node {
const [ariaHidden, setAriaHidden] = useState(false);
return (
Enable aria-hidden
setAriaHidden(!ariaHidden)}
accessibilityLabel="Enable aria-hidden"
/>
View with Text content
Regular Text
console.log('Pressed')}
aria-label={
ariaHidden ? 'This should be hidden' : 'This should be accessible'
}
style={styles.button}>
Pressable with text content
);
}
exports.title = 'Accessibility';
exports.documentationURL = 'https://reactnative.dev/docs/accessibilityinfo';
exports.description = 'Examples of using Accessibility APIs.';
exports.examples = [
{
title: 'Accessibility expanded',
render(): React.MixedElement {
return ;
},
},
{
title: 'Accessibility elements',
render(): React.MixedElement {
return ;
},
},
{
title: 'Automatic Content Grouping',
render(): React.MixedElement {
return ;
},
},
{
title: 'New accessibility roles and states',
render(): React.MixedElement {
return ;
},
},
{
title: 'Accessibility action examples',
render(): React.MixedElement {
return ;
},
},
{
title: 'Fake Slider Example',
render(): React.MixedElement {
return ;
},
},
{
title: 'Fake SliderExample For AccessibilityValue',
render(): React.MixedElement {
return ;
},
},
{
title: 'Check if the display options are enabled',
render(): React.MixedElement {
return ;
},
},
{
title: 'Check if the screen reader announces',
render(): React.MixedElement {
return ;
},
},
{
title: 'Check if accessibility is focused',
render(): React.MixedElement {
return ;
},
},
{
title: 'Check if these properties are enabled',
render(): React.MixedElement {
return ;
},
},
{
title: 'Testing importantForAccessibility',
render(): React.MixedElement {
return ;
},
},
{
title:
'Check if accessibilityState disabled is announced when the screenreader focus moves on the image',
render(): React.MixedElement {
return (
);
},
},
{
title: 'TextInput with aria-labelledby attribute',
render(): React.MixedElement {
return ;
},
},
{
title: 'accessibilityOrder',
render(): React.MixedElement {
return ;
},
},
{
title: 'Links in text',
render(): React.MixedElement {
return ;
},
},
{
title: 'Label coopting',
render(): React.MixedElement {
return ;
},
},
{
title: 'aria-hidden',
render(): React.MixedElement {
return ;
},
},
] as Array;