Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pink-comics-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/select': patch
---

Fixes `Select` so that when an `aria-label` is added, VO announces both the value and the label when there is a value.
1 change: 1 addition & 0 deletions packages/select/src/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const LiveExample: StoryFn<SelectProps> = ({
)}
// eslint-disable-next-line no-console
onChange={v => console.log(v)}
aria-label="hello world"
/>
);
LiveExample.parameters = {
Expand Down
45 changes: 45 additions & 0 deletions packages/select/src/Select/Select.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,51 @@ describe('packages/select', () => {
});
});

test('composes aria-label with selected value for screen readers', () => {
const { getInput } = renderSelect({
label: undefined,
'aria-label': 'Color',
defaultValue: Color.Blue,
});

const button = getInput();
expect(button).toHaveAttribute('aria-label', 'Color, Blue');
});

test('composes aria-label with placeholder when no value is selected', () => {
const { getInput } = renderSelect({
label: undefined,
'aria-label': 'Color',
placeholder: 'Choose a color',
});

const button = getInput();
expect(button).toHaveAttribute('aria-label', 'Color, Choose a color');
});

test('does not compose aria-label when a visible label is present', () => {
const { getInput } = renderSelect({
label: 'Color',
'aria-label': 'Color',
defaultValue: Color.Blue,
});

const button = getInput();
expect(button).not.toHaveAttribute('aria-label');
});

test('does not compose aria-label when aria-labelledby is present', () => {
const { getInput } = renderSelect({
label: undefined,
'aria-label': 'Color',
'aria-labelledby': 'custom-label',
defaultValue: Color.Blue,
});

const button = getInput();
expect(button).not.toHaveAttribute('aria-label');
});

test('option & group passes through data props', async () => {
const { queryByTestId } = render(
<Select label="Label">
Expand Down
18 changes: 17 additions & 1 deletion packages/select/src/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,22 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(
}),
} as const;

/**
* When aria-label is provided, we compose it with the selected value for screen readers. This is the default
* behavior for native select elements. When not doing this, only aria-label was being announced even when a value
* was selected.
*/
const composedAriaLabel = useMemo(() => {
if (!ariaLabel || label || ariaLabelledby) {
return undefined;
}

const selectedText =
selectedOption !== null ? selectedOption.props.children : placeholder;

return `${ariaLabel}, ${selectedText}`;
}, [ariaLabel, ariaLabelledby, label, placeholder, selectedOption]);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit hacky, but I'm not sure how else we would achieve this. Definitely open to ideas here if there's a better way to do it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we would set aria-current in addition to the label? AFAIK that should also be announced


return (
<LeafyGreenProvider darkMode={darkMode}>
<div
Expand Down Expand Up @@ -618,7 +634,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(
onOpen={onOpen}
onClose={onClose}
aria-labelledby={labelId}
aria-label={!label && !ariaLabelledby ? ariaLabel : undefined}
aria-label={composedAriaLabel}
aria-controls={menuId}
aria-expanded={open}
aria-describedby={descriptionId}
Expand Down
Loading