fix(react): added aria-hidden fallback for decorative icons (#2158)

This commit is contained in:
Jakob Guddas
2025-04-17 21:02:05 +02:00
committed by GitHub
parent 99d992678a
commit a11ba9e1cd
6 changed files with 83 additions and 1 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
import { createElement, forwardRef } from 'react';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses } from '@lucide/shared';
import { mergeClasses, hasA11yProp } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
@@ -46,6 +46,7 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
className: mergeClasses('lucide', className),
...(!children && !hasA11yProp(rest) && { 'aria-hidden': 'true' }),
...rest,
},
[
+60
View File
@@ -31,3 +31,63 @@ describe('Using Icon Component', () => {
expect(container.firstChild).toMatchSnapshot();
});
});
describe('Icon Component Accessibility', () => {
it('should not have aria-hidden prop when aria prop is present', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
aria-label="Air conditioning"
/>,
);
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should not have aria-hidden prop when title prop is present', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
// @ts-expect-error
title="Air conditioning"
/>,
);
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should not have aria-hidden prop when there are children that could be a <title> element', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
>
<title>Some title</title>
</Icon>,
);
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should never override aria-hidden prop', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
aria-hidden={false}
/>,
);
expect(container.firstChild).toHaveAttribute('aria-hidden', 'false');
});
});
@@ -2,6 +2,7 @@
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
aria-hidden="true"
class="lucide"
fill="none"
height="48"
@@ -2,6 +2,7 @@
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-air-vent lucide-AirVent"
fill="none"
height="24"
@@ -30,6 +31,7 @@ exports[`Using createLucideIcon > should create a component from an iconNode 1`]
exports[`Using createLucideIcon > should create a component from an iconNode with iconName 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-air-vent"
fill="none"
height="24"
@@ -58,6 +60,7 @@ exports[`Using createLucideIcon > should create a component from an iconNode wit
exports[`Using createLucideIcon > should include backwards compatible className 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-layout2 lucide-layout-2"
fill="none"
height="24"
@@ -11,6 +11,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
>
<rect width="18"
height="18"
@@ -41,6 +42,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
>
<rect width="18"
height="18"
@@ -71,6 +73,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
>
<rect width="18"
height="18"
+14
View File
@@ -49,3 +49,17 @@ export const mergeClasses = <ClassType = string | undefined | null>(...classes:
})
.join(' ')
.trim();
/**
* Check if a component has an accessibility prop
*
* @param {object} props
* @returns {boolean} Whether the component has an accessibility prop
*/
export const hasA11yProp = (props: Record<string, any>) => {
for (const prop in props) {
if (prop.startsWith('aria-') || prop === 'role' || prop === 'title') {
return true;
}
}
};