Skip to content
Prev Previous commit
Next Next commit
fix(ui): lint decorative custom icons for aria-hidden
  • Loading branch information
dvail committed Oct 1, 2026
commit 679a34fec96bd0c2345102f243e4b3cf7d059829
43 changes: 43 additions & 0 deletions ui/apps/platform/eslint-plugins/pluginAccessibility.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/* globals module */

function getJSXElementName(node) {
return node?.openingElement?.name?.name;
}

function hasJSXAttribute(node, attributeName) {
return node?.openingElement?.attributes?.some(
(attribute) => attribute.name?.name === attributeName
);
}

function getJSXAttribute(node, attributeName) {
return node?.attributes?.find((attribute) => attribute.name?.name === attributeName);
}
Expand Down Expand Up @@ -338,6 +348,39 @@ const rules = {
};
},
},
'customIcon-ariaHidden': {
// Decorative custom icons duplicate nearby status text and should be hidden from assistive technology.
meta: {
type: 'problem',
docs: {
description:
'Require aria-hidden on JSX elements passed to PatternFly customIcon props',
},
schema: [],
},
create(context) {
return {
JSXAttribute(node) {
if (node.name?.name !== 'customIcon') {
return;
}

const customIconElement = node.value?.expression;
if (
customIconElement?.type === 'JSXElement' &&
getJSXElementName(customIconElement) &&
!hasJSXAttribute(customIconElement, 'aria-hidden')
) {
context.report({
node: customIconElement.openingElement,
message:
'Add aria-hidden to decorative customIcon elements so assistive technologies do not announce duplicated status text',
});
}
},
};
},
},
'Button-Tooltip-isAriaDisabled': {
// Derived from review feedback; see PR #22653.
//
Expand Down
41 changes: 41 additions & 0 deletions ui/apps/platform/eslint-plugins/pluginAccessibility.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,47 @@ const plugin = require('./pluginAccessibility');

const ruleTester = createRuleTester();

// Test the customIcon-ariaHidden rule
ruleTester.run('customIcon-ariaHidden', plugin.rules['customIcon-ariaHidden'], {
valid: [
{
name: 'customIcon element has aria-hidden',
code: `<Alert customIcon={<Spinner aria-hidden />} title="Waiting" />`,
},
{
name: 'customIcon expression is not JSX',
code: `<Alert customIcon={statusIcon} title="Waiting" />`,
},
{
name: 'non-customIcon JSX attribute is ignored',
code: `<Alert icon={<Spinner />} title="Waiting" />`,
},
],

invalid: [
{
name: 'customIcon spinner is missing aria-hidden',
code: `<Alert customIcon={<Spinner />} title="Waiting" />`,
errors: [
{
message:
'Add aria-hidden to decorative customIcon elements so assistive technologies do not announce duplicated status text',
},
],
},
{
name: 'customIcon icon with other props is missing aria-hidden',
code: `<Alert customIcon={<CheckCircleIcon color="green" />} title="Complete" />`,
errors: [
{
message:
'Add aria-hidden to decorative customIcon elements so assistive technologies do not announce duplicated status text',
},
],
},
],
});

// Test the Button-Tooltip-isAriaDisabled rule
ruleTester.run('Button-Tooltip-isAriaDisabled', plugin.rules['Button-Tooltip-isAriaDisabled'], {
valid: [
Expand Down