diff --git a/src/components/uis/Button/Button.test.tsx b/src/components/uis/Button/Button.test.tsx
index 7370079..45621d7 100644
--- a/src/components/uis/Button/Button.test.tsx
+++ b/src/components/uis/Button/Button.test.tsx
@@ -545,6 +545,80 @@ describe('[Button]', () => {
});
});
+ describe('[Button] accessibility', () => {
+ it('should have accessibilityRole="button"', () => {
+ testingLib = render(
+ Component({
+ props: {
+ testID: 'a11y-button',
+ text: 'Click me',
+ },
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-button');
+ expect(button.props.accessibilityRole).toBe('button');
+ });
+
+ it('should use text as default accessibilityLabel', () => {
+ testingLib = render(
+ Component({
+ props: {
+ testID: 'a11y-button',
+ text: 'Submit',
+ },
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-button');
+ expect(button.props.accessibilityLabel).toBe('Submit');
+ });
+
+ it('should use custom accessibilityLabel when provided', () => {
+ testingLib = render(
+ Component({
+ props: {
+ testID: 'a11y-button',
+ text: 'Submit',
+ accessibilityLabel: 'Submit form',
+ },
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-button');
+ expect(button.props.accessibilityLabel).toBe('Submit form');
+ });
+
+ it('should have undefined accessibilityLabel when text is ReactElement', () => {
+ testingLib = render(
+ Component({
+ props: {
+ testID: 'a11y-button',
+ text: Custom,
+ },
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-button');
+ expect(button.props.accessibilityLabel).toBeUndefined();
+ });
+
+ it('should allow custom accessibilityLabel with ReactElement text', () => {
+ testingLib = render(
+ Component({
+ props: {
+ testID: 'a11y-button',
+ text: Custom,
+ accessibilityLabel: 'Custom button',
+ },
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-button');
+ expect(button.props.accessibilityLabel).toBe('Custom button');
+ });
+ });
+
describe('[Button] custom theme', () => {
it('should render with custom primary color from theme', () => {
const customTheme = {
diff --git a/src/components/uis/Button/Button.tsx b/src/components/uis/Button/Button.tsx
index 4ed5366..b94595a 100644
--- a/src/components/uis/Button/Button.tsx
+++ b/src/components/uis/Button/Button.tsx
@@ -51,6 +51,7 @@ export type Props = {
>;
hitSlop?: null | Insets | number | undefined;
hapticFeedback?: Haptics.ImpactFeedbackStyle;
+ accessibilityLabel?: string;
};
type Styles = {
@@ -241,6 +242,7 @@ export function Button({
loadingColor,
hitSlop = {top: 8, bottom: 8, left: 8, right: 8},
hapticFeedback,
+ accessibilityLabel,
}: Props): ReactElement {
const ref = useRef>(null);
const hovered = useHover(ref);
@@ -410,6 +412,8 @@ export function Button({
return (
{
expect(checkbox).toBeTruthy();
});
});
+
+ describe('accessibility', () => {
+ it('should have accessibilityRole="checkbox"', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept terms',
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect(json).toBeTruthy();
+ // The root element should have accessibilityRole
+ expect((json as any).props.accessibilityRole).toBe('checkbox');
+ });
+
+ it('should use text as default accessibilityLabel', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept terms',
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityLabel).toBe('Accept terms');
+ });
+
+ it('should use custom accessibilityLabel when provided', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept',
+ accessibilityLabel: 'Accept terms and conditions',
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityLabel).toBe('Accept terms and conditions');
+ });
+
+ it('should have accessibilityState with checked=true when checked', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept terms',
+ checked: true,
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityState).toEqual({
+ checked: true,
+ disabled: false,
+ });
+ });
+
+ it('should have accessibilityState with checked=false when unchecked', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept terms',
+ checked: false,
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityState).toEqual({
+ checked: false,
+ disabled: false,
+ });
+ });
+
+ it('should have accessibilityState with disabled=true when disabled', () => {
+ testingLib = render(
+ Component({
+ text: 'Accept terms',
+ disabled: true,
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityState).toEqual({
+ checked: false,
+ disabled: true,
+ });
+ });
+
+ it('should have undefined accessibilityLabel when text is ReactElement', () => {
+ testingLib = render(
+ Component({
+ text: Custom text,
+ }),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityLabel).toBeUndefined();
+ });
+ });
});
diff --git a/src/components/uis/Checkbox/Checkbox.tsx b/src/components/uis/Checkbox/Checkbox.tsx
index 203b0e3..e317017 100644
--- a/src/components/uis/Checkbox/Checkbox.tsx
+++ b/src/components/uis/Checkbox/Checkbox.tsx
@@ -38,6 +38,7 @@ export interface CheckboxProps {
checked?: boolean;
text?: string | ReactElement;
direction?: 'left' | 'right';
+ accessibilityLabel?: string;
}
const Container = styled(TouchableOpacity)`
@@ -77,6 +78,7 @@ export function Checkbox({
disabled = false,
checked = false,
onPress,
+ accessibilityLabel,
}: CheckboxProps): ReactElement {
const checkboxSize = typeof size === 'number'
? size
@@ -188,6 +190,9 @@ export function Checkbox({
return (
(
decoration = 'underline',
colors = {},
required = false,
+ accessibilityLabel,
}: EditTextProps,
ref,
): ReactElement => {
@@ -410,6 +412,7 @@ export const EditText = forwardRef(
})
: startElement}
{
expect(json).toBeTruthy();
});
+
+ describe('accessibility', () => {
+ it('should have accessibilityRole="button"', () => {
+ testingLib = render(
+ Component({
+ testID: 'a11y-icon-button',
+ icon: 'Plus',
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-icon-button');
+ expect(button.props.accessibilityRole).toBe('button');
+ });
+
+ it('should use icon name as default accessibilityLabel', () => {
+ testingLib = render(
+ Component({
+ testID: 'a11y-icon-button',
+ icon: 'Plus',
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-icon-button');
+ expect(button.props.accessibilityLabel).toBe('Plus');
+ });
+
+ it('should use custom accessibilityLabel when provided', () => {
+ testingLib = render(
+ Component({
+ testID: 'a11y-icon-button',
+ icon: 'Plus',
+ accessibilityLabel: 'Add new item',
+ }),
+ );
+
+ const button = testingLib.getByTestId('a11y-icon-button');
+ expect(button.props.accessibilityLabel).toBe('Add new item');
+ });
+ });
});
diff --git a/src/components/uis/IconButton/IconButton.tsx b/src/components/uis/IconButton/IconButton.tsx
index 1122198..71a35db 100644
--- a/src/components/uis/IconButton/IconButton.tsx
+++ b/src/components/uis/IconButton/IconButton.tsx
@@ -99,6 +99,7 @@ export type IconButtonProps = {
activeOpacity?: number;
touchableHighlightProps?: Omit;
hapticFeedback?: Haptics.ImpactFeedbackStyle;
+ accessibilityLabel?: string;
};
export function IconButton({
@@ -117,6 +118,7 @@ export function IconButton({
activeOpacity = 0.95,
touchableHighlightProps,
hapticFeedback,
+ accessibilityLabel,
}: IconButtonProps): ReactElement {
const ref = useRef>(null);
const hovered = useHover(ref);
@@ -245,6 +247,8 @@ export function IconButton({
return (
();
const fadeAnim = useRef(new Animated.Value(selected ? 1 : 0)).current;
@@ -131,6 +133,9 @@ export default function RadioButton({
return (
{
expect(baseElement).toBeTruthy();
});
});
+
+ describe('accessibility', () => {
+ it('should have accessibilityRole="radio" on RadioButton', async () => {
+ component = createComponent(
+ ,
+ );
+
+ testingLib = render(component);
+
+ const radioButton = testingLib.getByTestId('radio-a11y');
+ expect(radioButton.props.accessibilityRole).toBe('radio');
+ });
+
+ it('should use label as default accessibilityLabel', async () => {
+ component = createComponent(
+ ,
+ );
+
+ testingLib = render(component);
+
+ const radioButton = testingLib.getByTestId('radio-a11y');
+ expect(radioButton.props.accessibilityLabel).toBe('Option 1');
+ });
+
+ it('should use custom accessibilityLabel when provided', async () => {
+ component = createComponent(
+ ,
+ );
+
+ testingLib = render(component);
+
+ const radioButton = testingLib.getByTestId('radio-a11y');
+ expect(radioButton.props.accessibilityLabel).toBe('Select option one');
+ });
+
+ it('should have accessibilityState with selected=true when selected', async () => {
+ component = createComponent(
+ ,
+ );
+
+ testingLib = render(component);
+
+ const radioButton = testingLib.getByTestId('radio-a11y');
+ expect(radioButton.props.accessibilityState).toEqual({
+ selected: true,
+ disabled: false,
+ });
+ });
+
+ it('should have accessibilityState with disabled=true when disabled', async () => {
+ component = createComponent(
+ ,
+ );
+
+ testingLib = render(component);
+
+ const radioButton = testingLib.getByTestId('radio-a11y');
+ expect(radioButton.props.accessibilityState).toEqual({
+ selected: undefined,
+ disabled: true,
+ });
+ });
+ });
});
diff --git a/src/components/uis/SegmentedControl/SegmentedControl.tsx b/src/components/uis/SegmentedControl/SegmentedControl.tsx
index 821741b..2c373b3 100644
--- a/src/components/uis/SegmentedControl/SegmentedControl.tsx
+++ b/src/components/uis/SegmentedControl/SegmentedControl.tsx
@@ -169,6 +169,9 @@ function SegmentedControlContainer({
handlePress(item.value)}
diff --git a/src/components/uis/SwitchToggle/SwitchToggle.test.tsx b/src/components/uis/SwitchToggle/SwitchToggle.test.tsx
index 5f855ce..02f0085 100644
--- a/src/components/uis/SwitchToggle/SwitchToggle.test.tsx
+++ b/src/components/uis/SwitchToggle/SwitchToggle.test.tsx
@@ -102,4 +102,48 @@ describe('[SwitchToggle]', () => {
expect(baseElement).toBeTruthy();
});
});
+
+ describe('accessibility', () => {
+ it('should have accessibilityRole="switch"', () => {
+ testingLib = render(
+ createComponent(),
+ );
+
+ const switchElement = testingLib.getByRole('switch');
+ expect(switchElement).toBeTruthy();
+ });
+
+ it('should have accessibilityState with checked=true when isOn', () => {
+ testingLib = render(
+ createComponent(),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityState).toEqual({checked: true});
+ });
+
+ it('should have accessibilityState with checked=false when not isOn', () => {
+ testingLib = render(
+ createComponent(),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityState).toEqual({checked: false});
+ });
+
+ it('should use custom accessibilityLabel when provided', () => {
+ testingLib = render(
+ createComponent(
+ ,
+ ),
+ );
+
+ const json = testingLib.toJSON();
+ expect((json as any).props.accessibilityLabel).toBe('Toggle dark mode');
+ });
+ });
});
diff --git a/src/components/uis/SwitchToggle/SwitchToggle.tsx b/src/components/uis/SwitchToggle/SwitchToggle.tsx
index eec7dcc..2615d66 100644
--- a/src/components/uis/SwitchToggle/SwitchToggle.tsx
+++ b/src/components/uis/SwitchToggle/SwitchToggle.tsx
@@ -31,6 +31,7 @@ type Props = {
onElement?: any;
offElement?: any;
onPress?: () => void;
+ accessibilityLabel?: string;
};
// Using AnimatedView created above - functionally identical to styled(Animated.View)
@@ -88,6 +89,7 @@ export function SwitchToggle({
size = 'medium',
offElement,
onPress,
+ accessibilityLabel,
}: Props): ReactElement {
const {theme} = useTheme();
@@ -287,7 +289,9 @@ export function SwitchToggle({
return (