feat(packages): Added aria-hidden fallback for decorative icons to all packages (#3604)

* Add aria hidden to lucide package

* Add aria-hidden to astro package

* Add tests for shared package

* Fix test for props

* add aria prop to lucide-solid

* Add aria-hidden to lucide-vue-next

* Add aria-hidden prop to angular package

* Fix builds

* Add notice about aria-label in docs

* Format code

* Update packages/svelte/tests/Icon.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/lucide-svelte/tests/Icon.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/shared/src/utils/hasA11yProp.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Adjusted docs

* Fix import

* update tests

* test(shared): fix hasA11yProp unit test

* fix(packages/lucide-angular): fix hasA11yProp import path (non-relative import path will not get properly resolved by ng-packagr)

* Format code

* Adjust aria props to root element

* Format code

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
This commit is contained in:
Eric Fennis
2026-01-20 15:55:02 +01:00
committed by GitHub
parent 9b10d1d9bb
commit b6ed43d48c
77 changed files with 1192 additions and 507 deletions

View File

@@ -46,7 +46,7 @@
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@preact/preset-vite": "^2.10.2",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/preact": "^3.2.3",
"jest-serializer-html": "^7.1.0",
"preact": "^10.27.3",

View File

@@ -1,6 +1,7 @@
import { h, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import type { IconNode, LucideProps } from './types';
import { hasA11yProp } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
@@ -42,6 +43,7 @@ const Icon = ({
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', classes].join(' '),
...(!children && !hasA11yProp(rest) && { 'aria-hidden': 'true' }),
...rest,
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],

View File

@@ -31,3 +31,62 @@ 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
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');
});
});

View File

@@ -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"

View File

@@ -2,6 +2,7 @@
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-air-vent"
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"

View File

@@ -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"