Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/fix-iconbutton-aria-label-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clickhouse/click-ui': patch
---

Fix IconButton silently overriding a consumer-supplied aria-label with the icon name. A consumer-provided aria-label now wins, falling back to the icon name when none is given (including an empty string). The inner icon is also marked aria-hidden so the button exposes a single accessible name instead of two.
32 changes: 32 additions & 0 deletions src/components/IconButton/IconButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ describe('Button', () => {
expect(button).toBeDisabled();
});

describe('accessible name', () => {
it('should fall back to the icon name when no aria-label is given', () => {
const { getByRole } = renderButton({ icon: 'trash' });
expect(getByRole('button')).toHaveAttribute('aria-label', 'trash');
});

it('should use a consumer-supplied aria-label instead of the icon name', () => {
const { getByRole } = renderButton({
icon: 'trash',
'aria-label': 'Delete item',
});
expect(getByRole('button')).toHaveAttribute('aria-label', 'Delete item');
});

it('should fall back to the icon name when aria-label is an empty string', () => {
const { getByRole } = renderButton({
icon: 'trash',
'aria-label': '',
});
expect(getByRole('button')).toHaveAttribute('aria-label', 'trash');
});

it('should hide the inner icon from assistive tech so the button exposes a single accessible name', () => {
const { getByRole, queryByRole } = renderButton({
icon: 'trash',
'aria-label': 'Delete item',
});
expect(getByRole('button')).toHaveAttribute('aria-label', 'Delete item');
expect(queryByRole('img')).not.toBeInTheDocument();
});
});

describe('Button HTML types', () => {
it('should not default to any type when consumer does not specify it, thus behaving as type=submit', () => {
const handleSubmit = vi.fn();
Expand Down
3 changes: 2 additions & 1 deletion src/components/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
disabled={disabled}
ref={ref}
role="button"
aria-label={iconName}
aria-label={props['aria-label'] || iconName}
>
<Icon
name={icon}
aria-hidden
size="sm"
/>
</button>
Expand Down