From 9540ba7f1f7caaf7fc64dc1b64509926f09deff9 Mon Sep 17 00:00:00 2001 From: Arun Krishnamurthy Date: Thu, 22 Oct 2026 00:11:43 -0500 Subject: [PATCH 1/3] Report a disabled Switch as disabled to iOS accessibility On iOS, the traits derived from the role and `accessibilityState` are written onto the inner `UISwitch` and replace UIKit's own `NotEnabled` trait. The iOS branch of `Switch.js` never folded `disabled` into `accessibilityState`, so a disabled Switch was reported as enabled to VoiceOver and XCUITest. Share the Android branch's existing logic with iOS. --- .../Libraries/Components/Switch/Switch.js | 17 +++---- .../Switch/__tests__/Switch-test.js | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js diff --git a/packages/react-native/Libraries/Components/Switch/Switch.js b/packages/react-native/Libraries/Components/Switch/Switch.js index bf284ba57789..599b1d203177 100644 --- a/packages/react-native/Libraries/Components/Switch/Switch.js +++ b/packages/react-native/Libraries/Components/Switch/Switch.js @@ -227,16 +227,16 @@ const Switch: component( } }, [value, native]); + const {accessibilityState} = restProps; + const _disabled = disabled != null ? disabled : accessibilityState?.disabled; + + const _accessibilityState: ?AccessibilityState = + _disabled !== accessibilityState?.disabled + ? {...accessibilityState, disabled: _disabled} + : accessibilityState; + if (Platform.OS === 'android') { const {onTintColor, tintColor, ...androidProps} = restProps; - const {accessibilityState} = androidProps; - const _disabled = - disabled != null ? disabled : accessibilityState?.disabled; - - const _accessibilityState: ?AccessibilityState = - _disabled !== accessibilityState?.disabled - ? {...accessibilityState, disabled: _disabled} - : accessibilityState; const platformProps = { accessibilityState: _accessibilityState, @@ -262,6 +262,7 @@ const Switch: component( ); } else { const platformProps = { + accessibilityState: _accessibilityState, disabled, onTintColor: trackColorForTrue, style: StyleSheet.compose( diff --git a/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js b/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js new file mode 100644 index 000000000000..d4e71ac65b84 --- /dev/null +++ b/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js @@ -0,0 +1,47 @@ +/** + * 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'; + +const Switch = require('../Switch').default; +const {create} = require('@react-native/jest-preset/jest/renderer'); +const React = require('react'); + +// Jest renders the iOS branch of Switch; Switch-itest.js covers Android. +describe('Switch', () => { + it('reflects the disabled state via accessibilityState', async () => { + const component = await create(); + + expect(component.toJSON()?.props.accessibilityState).toEqual({ + disabled: true, + }); + }); + + it('lets disabled override accessibilityState.disabled', async () => { + const component = await create( + , + ); + + expect(component.toJSON()?.props.accessibilityState).toEqual({ + disabled: false, + busy: true, + }); + }); + + it('does not set accessibilityState when disabled is not set', async () => { + const component = await create(); + + expect(component.toJSON()?.props.accessibilityState).toBeUndefined(); + }); +}); From 8180861579afe6c05e3773c21d80d7c52e2538dd Mon Sep 17 00:00:00 2001 From: Arun Krishnamurthy Date: Thu, 22 Oct 2026 08:08:00 -0500 Subject: [PATCH 2/3] Extract accessibilityState with the other Switch props --- packages/react-native/Libraries/Components/Switch/Switch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/Switch/Switch.js b/packages/react-native/Libraries/Components/Switch/Switch.js index 599b1d203177..5c296fbcd02b 100644 --- a/packages/react-native/Libraries/Components/Switch/Switch.js +++ b/packages/react-native/Libraries/Components/Switch/Switch.js @@ -177,6 +177,7 @@ const Switch: component( ...SwitchProps, }): React.Node { const { + accessibilityState, disabled, ios_backgroundColor, onChange, @@ -227,7 +228,6 @@ const Switch: component( } }, [value, native]); - const {accessibilityState} = restProps; const _disabled = disabled != null ? disabled : accessibilityState?.disabled; const _accessibilityState: ?AccessibilityState = From 7968c9eee1bca9d3cac653449989d77b40bcc5e5 Mon Sep 17 00:00:00 2001 From: Arun Krishnamurthy Date: Fri, 23 Oct 2026 09:08:16 -0500 Subject: [PATCH 3/3] Move the Switch accessibilityState tests to Fantom --- .../Switch/__tests__/Switch-itest.js | 24 ++++++++++ .../Switch/__tests__/Switch-test.js | 47 ------------------- 2 files changed, 24 insertions(+), 47 deletions(-) delete mode 100644 packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js diff --git a/packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js b/packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js index a97753e4b268..1c37adb76974 100644 --- a/packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js +++ b/packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js @@ -46,6 +46,30 @@ describe('Switch', () => { ); }); + it('lets disabled override accessibilityState.disabled', () => { + const root = render( + , + ); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('does not set accessibilityState when disabled is not set', () => { + const root = render(); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual(); + }); + it('renders with track and thumb color props applied', () => { // Exercises the color-resolution path; the mounted switch still renders as // the native switch host. diff --git a/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js b/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js deleted file mode 100644 index d4e71ac65b84..000000000000 --- a/packages/react-native/Libraries/Components/Switch/__tests__/Switch-test.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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'; - -const Switch = require('../Switch').default; -const {create} = require('@react-native/jest-preset/jest/renderer'); -const React = require('react'); - -// Jest renders the iOS branch of Switch; Switch-itest.js covers Android. -describe('Switch', () => { - it('reflects the disabled state via accessibilityState', async () => { - const component = await create(); - - expect(component.toJSON()?.props.accessibilityState).toEqual({ - disabled: true, - }); - }); - - it('lets disabled override accessibilityState.disabled', async () => { - const component = await create( - , - ); - - expect(component.toJSON()?.props.accessibilityState).toEqual({ - disabled: false, - busy: true, - }); - }); - - it('does not set accessibilityState when disabled is not set', async () => { - const component = await create(); - - expect(component.toJSON()?.props.accessibilityState).toBeUndefined(); - }); -});