You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/07/31 22:05:39 UTC
(superset) branch master updated: fix(a11y): add aria-label to dashboard IconButton and unlabeled call sites (#41470)
This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new b452c1634dc fix(a11y): add aria-label to dashboard IconButton and unlabeled call sites (#41470)
b452c1634dc is described below
commit b452c1634dcacddf01466625a608a0bf7260ca7d
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Fri Jul 31 15:05:32 2026 -0700
fix(a11y): add aria-label to dashboard IconButton and unlabeled call sites (#41470)
Co-authored-by: Claude Sonnet 4.6 <no...@anthropic.com>
---
...ntButton.tsx => DeleteComponentButton.test.tsx} | 34 ++++++++++------------
.../dashboard/components/DeleteComponentButton.tsx | 3 ++
.../src/dashboard/components/IconButton.test.tsx | 14 +++++++++
.../src/dashboard/components/IconButton.tsx | 5 +++-
.../gridComponents/Column/Column.test.tsx | 11 ++++++-
.../components/gridComponents/Column/Column.tsx | 2 ++
.../gridComponents/Header/Header.test.tsx | 4 ++-
.../components/gridComponents/Row/Row.test.tsx | 9 ++++++
.../components/gridComponents/Row/Row.tsx | 2 ++
9 files changed, 63 insertions(+), 21 deletions(-)
diff --git a/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx b/superset-frontend/src/dashboard/components/DeleteComponentButton.test.tsx
similarity index 53%
copy from superset-frontend/src/dashboard/components/DeleteComponentButton.tsx
copy to superset-frontend/src/dashboard/components/DeleteComponentButton.test.tsx
index 37c24a180f3..519e31cdba7 100644
--- a/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx
+++ b/superset-frontend/src/dashboard/components/DeleteComponentButton.test.tsx
@@ -16,25 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { MouseEventHandler, FC } from 'react';
+import { render, screen, fireEvent } from 'spec/helpers/testing-library';
+import DeleteComponentButton from './DeleteComponentButton';
-import { Icons } from '@superset-ui/core/components/Icons';
-import type { IconType } from '@superset-ui/core/components/Icons/types';
-import IconButton from './IconButton';
+test('exposes an accessible name without rendering visible label text', () => {
+ render(<DeleteComponentButton onDelete={jest.fn()} />);
-type DeleteComponentButtonProps = {
- onDelete: MouseEventHandler<HTMLButtonElement>;
- iconSize?: IconType['iconSize'];
-};
+ expect(
+ screen.getByRole('button', { name: 'Delete component' }),
+ ).toBeInTheDocument();
+ expect(screen.queryByText('Delete component')).not.toBeInTheDocument();
+});
-const DeleteComponentButton: FC<DeleteComponentButtonProps> = ({
- onDelete,
- iconSize,
-}) => (
- <IconButton
- onClick={onDelete}
- icon={<Icons.DeleteOutlined iconSize={iconSize ?? 'l'} />}
- />
-);
+test('calls onDelete when clicked', () => {
+ const onDelete = jest.fn();
+ render(<DeleteComponentButton onDelete={onDelete} />);
-export default DeleteComponentButton;
+ fireEvent.click(screen.getByRole('button', { name: 'Delete component' }));
+
+ expect(onDelete).toHaveBeenCalledTimes(1);
+});
diff --git a/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx b/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx
index 37c24a180f3..65a440b10d3 100644
--- a/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx
+++ b/superset-frontend/src/dashboard/components/DeleteComponentButton.tsx
@@ -18,6 +18,7 @@
*/
import { MouseEventHandler, FC } from 'react';
+import { t } from '@apache-superset/core/translation';
import { Icons } from '@superset-ui/core/components/Icons';
import type { IconType } from '@superset-ui/core/components/Icons/types';
import IconButton from './IconButton';
@@ -33,6 +34,8 @@ const DeleteComponentButton: FC<DeleteComponentButtonProps> = ({
}) => (
<IconButton
onClick={onDelete}
+ label={t('Delete component')}
+ hideVisibleLabel
icon={<Icons.DeleteOutlined iconSize={iconSize ?? 'l'} />}
/>
);
diff --git a/superset-frontend/src/dashboard/components/IconButton.test.tsx b/superset-frontend/src/dashboard/components/IconButton.test.tsx
index 289cafd4a74..a021e63a275 100644
--- a/superset-frontend/src/dashboard/components/IconButton.test.tsx
+++ b/superset-frontend/src/dashboard/components/IconButton.test.tsx
@@ -73,3 +73,17 @@ test('renders the provided label', () => {
expect(screen.getByText('My Label')).toBeInTheDocument();
});
+
+test('hideVisibleLabel suppresses visible text but keeps the accessible name', () => {
+ render(
+ <IconButton
+ icon={icon}
+ onClick={jest.fn()}
+ label="My Label"
+ hideVisibleLabel
+ />,
+ );
+
+ expect(screen.queryByText('My Label')).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'My Label' })).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/dashboard/components/IconButton.tsx b/superset-frontend/src/dashboard/components/IconButton.tsx
index 3e48730cff1..8e5b842508d 100644
--- a/superset-frontend/src/dashboard/components/IconButton.tsx
+++ b/superset-frontend/src/dashboard/components/IconButton.tsx
@@ -22,6 +22,7 @@ import { styled, SupersetTheme } from '@apache-superset/core/theme';
interface IconButtonProps extends HTMLAttributes<HTMLButtonElement> {
icon: JSX.Element;
label?: string;
+ hideVisibleLabel?: boolean;
onClick: MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
'data-test'?: string;
@@ -63,6 +64,7 @@ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
{
icon,
label,
+ hideVisibleLabel,
onClick,
onKeyDown,
disabled,
@@ -75,6 +77,7 @@ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
{...rest}
ref={ref}
type="button"
+ aria-label={label}
isDisabled={disabled}
aria-disabled={disabled}
data-test={dataTest}
@@ -91,7 +94,7 @@ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
}}
>
{icon}
- {label && <StyledSpan>{label}</StyledSpan>}
+ {label && !hideVisibleLabel && <StyledSpan>{label}</StyledSpan>}
</StyledButton>
),
);
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Column/Column.test.tsx b/superset-frontend/src/dashboard/components/gridComponents/Column/Column.test.tsx
index c402942fe63..278548d213c 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Column/Column.test.tsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Column/Column.test.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { fireEvent, render } from 'spec/helpers/testing-library';
+import { fireEvent, render, screen } from 'spec/helpers/testing-library';
import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown';
import IconButton from 'src/dashboard/components/IconButton';
@@ -200,6 +200,15 @@ test('should call deleteComponent when deleted', () => {
expect(deleteComponent).toHaveBeenCalledTimes(1);
});
+test('settings IconButton exposes an accessible name without visible label text', () => {
+ setup({ component: columnWithoutChildren, editMode: true });
+
+ expect(
+ screen.getByRole('button', { name: 'Column settings' }),
+ ).toBeInTheDocument();
+ expect(screen.queryByText('Column settings')).not.toBeInTheDocument();
+});
+
test('should pass its own width as availableColumnCount to children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveTextContent(
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Column/Column.tsx b/superset-frontend/src/dashboard/components/gridComponents/Column/Column.tsx
index 72be4b59a67..edb36d1da85 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Column/Column.tsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Column/Column.tsx
@@ -247,6 +247,8 @@ const Column = (props: ColumnProps) => {
/>
<IconButton
onClick={() => handleChangeFocus(true)}
+ label={t('Column settings')}
+ hideVisibleLabel
icon={<Icons.SettingOutlined iconSize="m" />}
/>
</HoverMenu>
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/gridComponents/Header/Header.test.tsx
index 5c13f038f03..f51d74d14c0 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Header/Header.test.tsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Header/Header.test.tsx
@@ -145,7 +145,9 @@ describe('Header', () => {
const deleteComponent = jest.fn();
setup({ editMode: true, deleteComponent });
- const trashButton = screen.getByRole('button', { name: 'delete' });
+ const trashButton = screen.getByRole('button', {
+ name: 'Delete component',
+ });
fireEvent.click(trashButton);
expect(deleteComponent).toHaveBeenCalledTimes(1);
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row/Row.test.tsx b/superset-frontend/src/dashboard/components/gridComponents/Row/Row.test.tsx
index 69635012d20..39b2b718ec6 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Row/Row.test.tsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Row/Row.test.tsx
@@ -240,6 +240,15 @@ test('should call deleteComponent when deleted', () => {
expect(deleteComponent).toHaveBeenCalledTimes(1);
});
+test('settings IconButton exposes an accessible name without visible label text', () => {
+ setup({ component: rowWithoutChildren, editMode: true });
+
+ expect(
+ screen.getByRole('button', { name: 'Row settings' }),
+ ).toBeInTheDocument();
+ expect(screen.queryByText('Row settings')).not.toBeInTheDocument();
+});
+
test('should pass appropriate availableColumnCount to children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveTextContent(
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx b/superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx
index c5ec0c497f5..b644a65fef8 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx
@@ -293,6 +293,8 @@ const Row = memo((props: RowProps) => {
<DeleteComponentButton onDelete={handleDeleteComponent} />
<IconButton
onClick={() => handleChangeFocus(true)}
+ label={t('Row settings')}
+ hideVisibleLabel
icon={<Icons.SettingOutlined iconSize="l" />}
/>
</HoverMenu>