diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx
index 4507561a48..82874c6ad9 100644
--- a/src/components/Chip/Chip.tsx
+++ b/src/components/Chip/Chip.tsx
@@ -184,8 +184,10 @@ const Chip = ({
children,
icon,
avatar,
- selected = false,
+ selected,
disabled = false,
+ accessibilityState: _accessibilityState,
+ 'aria-checked': _ariaChecked,
background,
'aria-label': ariaLabel,
role = 'button',
@@ -300,7 +302,11 @@ const Chip = ({
disabled={disabled}
aria-label={ariaLabel}
role={role}
- aria-selected={selected}
+ {...(Platform.OS === 'web'
+ ? selected !== undefined
+ ? { 'aria-pressed': selected }
+ : undefined
+ : { 'aria-selected': selected ?? false })}
aria-disabled={disabled}
testID={testID}
theme={theme}
diff --git a/src/components/__tests__/Chip.test.tsx b/src/components/__tests__/Chip.test.tsx
index a7d522e4f0..061922809e 100644
--- a/src/components/__tests__/Chip.test.tsx
+++ b/src/components/__tests__/Chip.test.tsx
@@ -1,4 +1,6 @@
-import { describe, expect, it } from '@jest/globals';
+import { Platform } from 'react-native';
+
+import { afterEach, beforeEach, describe, expect, it } from '@jest/globals';
import color from 'color';
import { render, screen } from '../../test-utils';
@@ -309,3 +311,143 @@ describe('getChipColor - border color', () => {
});
});
});
+
+describe('accessibility', () => {
+ const originalPlatform = Platform.OS;
+
+ afterEach(() => {
+ Platform.OS = originalPlatform;
+ });
+
+ describe('web', () => {
+ beforeEach(() => {
+ Platform.OS = 'web';
+ });
+
+ it('does not expose aria-pressed for an action Chip when selected is omitted', async () => {
+ await render( {}}>Action Chip);
+
+ const button = screen.getByRole('button');
+ expect(button).not.toHaveProp('aria-pressed');
+ });
+
+ it('exposes aria-pressed as true when selected is true', async () => {
+ await render(
+ {}} selected={true}>
+ Selected Chip
+
+ );
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveProp('aria-pressed', true);
+ });
+
+ it('exposes aria-pressed as false when selected is explicitly false', async () => {
+ await render(
+ {}} selected={false}>
+ Unselected Chip
+
+ );
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveProp('aria-pressed', false);
+ });
+
+ it('removes aria-selected from the interactive button', async () => {
+ await render(
+ {}} selected={true}>
+ Toggle Chip
+
+ );
+
+ const button = screen.getByRole('button');
+ expect(button).not.toHaveProp('aria-selected');
+ });
+
+ it('removes aria-selected when selected is omitted', async () => {
+ await render( {}}>Action Chip);
+
+ const button = screen.getByRole('button');
+ expect(button).not.toHaveProp('aria-selected');
+ });
+ });
+
+ describe('native', () => {
+ it('preserves selected state when selected is true', async () => {
+ await render(
+ {}} selected={true}>
+ Selected Chip
+
+ );
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveProp(
+ 'accessibilityState',
+ expect.objectContaining({ selected: true })
+ );
+ });
+
+ it('produces native selected state false when selected is explicitly false', async () => {
+ await render(
+ {}} selected={false}>
+ Unselected Chip
+
+ );
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveProp(
+ 'accessibilityState',
+ expect.objectContaining({ selected: false })
+ );
+ });
+
+ it('produces native selected state false when selected is omitted', async () => {
+ await render( {}}>Default Chip);
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveProp(
+ 'accessibilityState',
+ expect.objectContaining({ selected: false })
+ );
+ });
+ });
+
+ it('does not forward accessibilityState or aria-checked to the outer Surface or interactive TouchableRipple', async () => {
+ await render(
+ {}}
+ testID="chip-touchable"
+ accessibilityState={{ checked: true, busy: true }}
+ aria-checked={true}
+ >
+ Chip
+
+ );
+
+ const touchable = screen.getByTestId('chip-touchable');
+ const surface = touchable.parent;
+
+ expect(surface).not.toHaveProp('accessibilityState');
+ expect(surface).not.toHaveProp('aria-checked');
+
+ expect(touchable).not.toHaveProp('aria-checked');
+ expect(touchable).toHaveProp(
+ 'accessibilityState',
+ expect.not.objectContaining({
+ checked: expect.anything(),
+ busy: expect.anything(),
+ })
+ );
+ });
+
+ it('does not forward accessibilityState or aria-checked to the interactive button by default', async () => {
+ await render( {}}>Default Chip);
+
+ const button = screen.getByRole('button');
+ expect(button).not.toHaveProp('aria-checked');
+ expect(button).toHaveProp(
+ 'accessibilityState',
+ expect.not.objectContaining({ checked: expect.anything() })
+ );
+ });
+});