diff --git a/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json b/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json
new file mode 100644
index 00000000000..baa9fd7b9bb
--- /dev/null
+++ b/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Implement accessibilityLevel for Fabric",
+ "packageName": "react-native-windows",
+ "email": "kvineeth@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx
index 2c3ffef60bb..7ef5860479c 100644
--- a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx
+++ b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx
@@ -22,14 +22,19 @@ class AccessibilityBaseExample extends React.Component {
The following has accessibilityLabel and accessibilityHint:
The following has accessible and accessibilityLabel:
The following has accessibilitySetSize, accessibilityPosInSet and
@@ -37,13 +42,15 @@ class AccessibilityBaseExample extends React.Component {
);
diff --git a/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts b/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts
index 68e4df380c1..d27af11844f 100644
--- a/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts
+++ b/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts
@@ -77,4 +77,31 @@ describe('Accessibility Tests', () => {
const dump = await dumpVisualTree('accessibilityValue-text');
expect(dump).toMatchSnapshot();
});
+ test('Accessibility data for Label,Level and Hint', async () => {
+ await searchBox('Lab');
+ const componentsTab = await app.findElementByTestID(
+ 'accessibility-base-view-1',
+ );
+ await componentsTab.waitForDisplayed({timeout: 5000});
+ const dump = await dumpVisualTree('accessibility-base-view-1');
+ expect(dump).toMatchSnapshot();
+ });
+ test('Accessibility data for Label and Level', async () => {
+ await searchBox('Lab');
+ const componentsTab = await app.findElementByTestID(
+ 'accessibility-base-view-2',
+ );
+ await componentsTab.waitForDisplayed({timeout: 5000});
+ const dump = await dumpVisualTree('accessibility-base-view-2');
+ expect(dump).toMatchSnapshot();
+ });
+ test('Accessibility data for Role, Setsize etc.', async () => {
+ await searchBox('Lab');
+ const componentsTab = await app.findElementByTestID(
+ 'accessibility-base-view-3',
+ );
+ await componentsTab.waitForDisplayed({timeout: 5000});
+ const dump = await dumpVisualTree('accessibility-base-view-3');
+ expect(dump).toMatchSnapshot();
+ });
});
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap
index cf7f392168f..1662d978eda 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap
@@ -1,5 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Accessibility Tests Accessibility data for Label and Level 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "accessibility-base-view-2",
+ "ControlType": 50026,
+ "Level": 2,
+ "LocalizedControlType": "group",
+ "Name": "A hint for the red box.",
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "A hint for the red box.",
+ "TestId": "accessibility-base-view-2",
+ },
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 0, 0, 255)",
+ },
+ "Comment": "accessibility-base-view-2",
+ "Offset": "0, 0, 0",
+ "Size": "50, 50",
+ "Visual Type": "SpriteVisual",
+ },
+}
+`;
+
+exports[`Accessibility Tests Accessibility data for Label,Level and Hint 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "accessibility-base-view-1",
+ "ControlType": 50026,
+ "HelpText": "A hint for the blue box.",
+ "Level": 1,
+ "LocalizedControlType": "group",
+ "Name": "A blue box",
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "A blue box",
+ "TestId": "accessibility-base-view-1",
+ },
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(0, 0, 255, 255)",
+ },
+ "Comment": "accessibility-base-view-1",
+ "Offset": "0, 0, 0",
+ "Size": "50, 50",
+ "Visual Type": "SpriteVisual",
+ },
+}
+`;
+
+exports[`Accessibility Tests Accessibility data for Role, Setsize etc. 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "accessibility-base-view-3",
+ "ControlType": 50007,
+ "Level": 9,
+ "LocalizedControlType": "list item",
+ "Name": "Aria label takes precedence",
+ "PositionInSet": 2,
+ "SizeofSet": 5,
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "Aria label takes precedence",
+ "TestId": "accessibility-base-view-3",
+ },
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 0, 0, 255)",
+ },
+ "Comment": "accessibility-base-view-3",
+ "Offset": "0, 0, 0",
+ "Size": "50, 50",
+ "Visual Type": "SpriteVisual",
+ },
+}
+`;
+
exports[`Accessibility Tests Components can store range data by setting the min, max, and now of accessibilityValue 1`] = `
{
"Automation Tree": {
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
index c2bdfc40a12..fdbd40728a8 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
@@ -10,6 +10,8 @@ exports[`snapshotAllPages Accessibility Windows 1`] = `
The following has accessible and accessibilityLabel:
The following has accessibilitySetSize, accessibilityPosInSet and accessibilityLabel:
`;
diff --git a/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp b/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp
index 7cff87dadb2..f0a935e1260 100644
--- a/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp
+++ b/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp
@@ -494,6 +494,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse(
BSTR name;
int positionInSet = 0;
int sizeOfSet = 0;
+ int level = 0;
LiveSetting liveSetting = LiveSetting::Off;
BSTR itemStatus;
@@ -511,6 +512,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse(
pTarget4->get_CurrentPositionInSet(&positionInSet);
pTarget4->get_CurrentSizeOfSet(&sizeOfSet);
pTarget4->get_CurrentLiveSetting(&liveSetting);
+ pTarget4->get_CurrentLevel(&level);
pTarget4->Release();
}
result.Insert(L"AutomationId", winrt::Windows::Data::Json::JsonValue::CreateStringValue(automationId));
@@ -523,6 +525,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse(
InsertStringValueIfNotEmpty(result, L"Name", name);
InsertIntValueIfNotDefault(result, L"PositionInSet", positionInSet);
InsertIntValueIfNotDefault(result, L"SizeofSet", sizeOfSet);
+ InsertIntValueIfNotDefault(result, L"Level", level);
InsertLiveSettingValueIfNotDefault(result, L"LiveSetting", liveSetting);
InsertStringValueIfNotEmpty(result, L"ItemStatus", itemStatus);
DumpUIAPatternInfo(pTarget, result);
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp
index ad5e31cff02..3bedab40f08 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp
@@ -559,6 +559,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT
: SysAllocString(L"");
break;
}
+ case UIA_LevelPropertyId: {
+ pRetVal->vt = VT_I4;
+ pRetVal->lVal = props->accessibilityLevel;
+ break;
+ }
}
return hr;
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
index 38af8995408..c23df8a9d06 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
@@ -804,6 +804,9 @@ void ComponentView::updateAccessibilityProps(
oldViewProps.accessibilityLiveRegion,
newViewProps.accessibilityLiveRegion);
+ winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
+ EnsureUiaProvider(), UIA_LevelPropertyId, oldViewProps.accessibilityLevel, newViewProps.accessibilityLevel);
+
if ((oldViewProps.accessibilityState.has_value() && oldViewProps.accessibilityState->selected.has_value()) !=
((newViewProps.accessibilityState.has_value() && newViewProps.accessibilityState->selected.has_value()))) {
auto compProvider =
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp
index 3b8666ab7b9..4c3aeb8e37a 100644
--- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp
@@ -40,6 +40,10 @@ HostPlatformViewProps::HostPlatformViewProps(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilitySetSize
: convertRawProp(context, rawProps, "accessibilitySetSize", sourceProps.accessibilitySetSize, 0)),
+ accessibilityLevel(
+ ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
+ ? sourceProps.accessibilityLevel
+ : convertRawProp(context, rawProps, "accessibilityLevel", sourceProps.accessibilityLevel, 0)),
accessibilityLiveRegion(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.accessibilityLiveRegion
: convertRawProp(
@@ -84,6 +88,7 @@ void HostPlatformViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(focusable);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityPosInSet);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilitySetSize);
+ RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLevel);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLiveRegion);
RAW_SET_PROP_SWITCH_CASE_BASIC(keyDownEvents);
RAW_SET_PROP_SWITCH_CASE_BASIC(keyUpEvents);
diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h
index 885dd8cbf18..0a48bc1e787 100644
--- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h
+++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h
@@ -27,6 +27,7 @@ class HostPlatformViewProps : public BaseViewProps {
int accessibilityPosInSet{0};
int accessibilitySetSize{0};
std::string accessibilityLiveRegion{"none"};
+ int accessibilityLevel{0};
// std::optional overflowAnchor{};
std::optional tooltip{};