From 4f5cf108d6aae01f0ad00e9ee06e1e6c2c947978 Mon Sep 17 00:00:00 2001 From: Fedo Hagge-Kubat Date: Mon, 2 Mar 2026 15:39:27 +0100 Subject: [PATCH 1/3] fix(a11y): add non-color visual cues to status indicators and error messages (WCAG 1.4.1) - Replace generic icons in AlertStatusIcon with distinct shapes per status: Success=CheckCircleOutlined, Error=CloseCircleOutlined, Working=LoadingOutlined, Noop=ClockCircleOutlined, Grace=ExclamationCircleOutlined - Add "Error:" text prefix to ChartErrorMessage and ImportModal ErrorAlert - Add aria-label with scheme name and role="option" to ColorSchemeLabel swatches - Mark individual color swatches as aria-hidden for cleaner screen reader output - Add tests verifying distinct icon rendering per status, icon presence in ErrorAlert, and aria-label on ColorSchemeLabel --- .../components/Chart/ChartErrorMessage.tsx | 3 +- .../ErrorMessage/ErrorAlert.test.tsx | 43 +++++++++++++++++++ .../src/components/ImportModal/ErrorAlert.tsx | 6 ++- .../ColorSchemeLabel.test.tsx | 16 +++++++ .../ColorSchemeControl/ColorSchemeLabel.tsx | 4 ++ .../alerts/components/AlertStatusIcon.tsx | 33 +++++++------- 6 files changed, 85 insertions(+), 20 deletions(-) diff --git a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx index cd21275153f2..f219bd0fa2f6 100644 --- a/superset-frontend/src/components/Chart/ChartErrorMessage.tsx +++ b/superset-frontend/src/components/Chart/ChartErrorMessage.tsx @@ -18,6 +18,7 @@ */ import { ClientErrorObject, SupersetError } from '@superset-ui/core'; +import { t } from '@apache-superset/core'; import { FC } from 'react'; import { useChartOwnerNames } from 'src/hooks/apiResources'; import { ErrorMessageWithStackTrace } from 'src/components'; @@ -32,7 +33,7 @@ export type Props = { stackTrace?: string; } & Omit; -const DEFAULT_CHART_ERROR = 'Data error'; +const DEFAULT_CHART_ERROR = t('Error: Data error'); export const ChartErrorMessage: FC = ({ chartId, error, ...props }) => { // fetches the chart owners and adds them to the extra data of the error message diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx index d95ec32ac38e..0e538052b30c 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx @@ -88,3 +88,46 @@ test('ErrorAlert renders compact mode with a tooltip and modal', () => { fireEvent.click(iconTrigger); expect(screen.getByText('Compact mode example')).toBeInTheDocument(); }); + +test('ErrorAlert renders an icon alongside the error text (non-color cue)', () => { + const { container } = render( + , + ); + // The Alert component with showIcon renders an antd icon element + const icon = container.querySelector('.ant-alert-icon'); + expect(icon).toBeInTheDocument(); +}); + +test('ErrorAlert renders a warning icon for warning type (non-color cue)', () => { + const { container } = render( + , + ); + // In compact mode, the renderTrigger function shows WarningOutlined for warnings + expect( + container.querySelector('[aria-label="warning"]'), + ).toBeInTheDocument(); +}); + +test('ErrorAlert renders an exclamation-circle icon for error type in compact mode (non-color cue)', () => { + const { container } = render( + , + ); + // In compact mode, the renderTrigger function shows ExclamationCircleOutlined for errors + expect( + container.querySelector('[aria-label="exclamation-circle"]'), + ).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/ImportModal/ErrorAlert.tsx b/superset-frontend/src/components/ImportModal/ErrorAlert.tsx index 24551678c386..101267a15f08 100644 --- a/superset-frontend/src/components/ImportModal/ErrorAlert.tsx +++ b/superset-frontend/src/components/ImportModal/ErrorAlert.tsx @@ -44,7 +44,11 @@ export const ErrorAlert: FunctionComponent = ({ css={(theme: SupersetTheme) => antdWarningAlertStyles(theme)} type="error" showIcon - message={errorMessage} + message={ + <> + {t('Error:')} {errorMessage} + + } description={ showDbInstallInstructions ? ( <> diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.test.tsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.test.tsx index e851984fe069..c53e8d2c02bf 100644 --- a/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.test.tsx +++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.test.tsx @@ -56,3 +56,19 @@ test('should render the colors', () => { const allColors = screen.getAllByTestId('color'); expect(allColors).toHaveLength(12); }); + +test('should render aria-label with scheme name on the swatch container', () => { + setup(); + const option = screen.getByRole('option', { + name: 'Color scheme: Superset Colors', + }); + expect(option).toBeInTheDocument(); +}); + +test('color swatches should be aria-hidden', () => { + setup(); + const allColors = screen.getAllByTestId('color'); + allColors.forEach(color => { + expect(color).toHaveAttribute('aria-hidden', 'true'); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.tsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.tsx index 60e0a190916c..f4e36d27a74b 100644 --- a/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.tsx +++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl/ColorSchemeLabel.tsx @@ -18,6 +18,7 @@ */ import { css, SupersetTheme } from '@apache-superset/core/theme'; +import { t } from '@apache-superset/core/translation'; import { useRef, useState } from 'react'; import { Tooltip } from '@superset-ui/core/components'; @@ -55,6 +56,7 @@ export default function ColorSchemeLabel(props: ColorSchemeLabelProps) {