Compare commits

...
Author SHA1 Message Date
Azuzulagithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Karsa
5a92b9ba26 feat(icons): added printer-3d icon (#4414)
* Added icons/printer3d.svg

* Added icons/printer3d.json

* Update icons/printer3d.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Rename printer3d.svg to printer-3d.svg

* Rename printer3d.json to printer-3d.json

* Modify SVG paths for printer-3D icon

* Update icons/printer-3d.svg

* Update printer-3d.json

* Update printer-3d.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-30 00:08:38 +02:00
Tanvir MahinandKarsa 66d8f9fc39 feat(icons): added bangladeshi-taka icon (#4869)
* feat(icons): added `bangladeshi-taka` icon

* chore(metadata): updated `bangladeshi-taka` metadata

* chore(metadata): updated use cases and tags

* Apply suggestion from @karsa-mistmere

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-09-24 15:16:39 +02:00
e042fec3b5 fix(packages): declare @types/react as an optional peer dependency (#4892)
`lucide-react` and `lucide-react-native` both ship `.d.ts` files that
import React types (`ForwardRefExoticComponent`, `SVGProps`,
`RefAttributes`, `ReactNode`), but neither package declares
`@types/react`. Under a hoisted `node_modules` this resolves by
accident, so the gap is invisible in normal testing.

It does not resolve under a strict layout — most notably pnpm's global
virtual store (`enableGlobalVirtualStore`), where each package is linked
from a store path outside the consuming repo. TypeScript resolves a
package's imports relative to where that package physically lives and
ignores `NODE_PATH`, so `react` resolves to the untyped
`react/index.js`. Every icon's props silently degrade to `any`, and
consumers hit implicit-any errors on their own callbacks under
`noImplicitAny`. `skipLibCheck: true` hides the errors inside the
`.d.ts` itself, which is why this survives most typechecks.

Declaring the typings package as a peer dependency (rather than a hard
dependency) lets it dedupe against whatever `@types/react` the consumer
already has. `peerDependenciesMeta` marks it optional so JS-only
consumers get no unmet-peer warning.

Verified by packing each package and installing the tarball into a
scratch consumer with `pnpm --virtual-store-dir` pointing outside the
consumer, then typechecking with `skipLibCheck: false` and a
`@ts-expect-error` probe on a nonexistent icon prop — a positive type
assertion keeps passing when types widen to `any`, so it would not
catch a regression. Before the change the probe reports
`TS2578: Unused '@ts-expect-error' directive` plus `TS7016` inside the
shipped `.d.ts`; after it, `tsc` is clean and `--traceResolution`
shows `react` resolving to `@types/react/index.d.ts` instead of
`react/index.js`. Existing peer ranges are unchanged and the lockfile
is unaffected.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-24 15:11:17 +02:00
Karsa c870b3c54a feat(icons): added letters icon (#4779)
* Added icons/letters.svg

* Added icons/letters.json

* Updated icons/letters.json
2026-09-24 13:40:20 +02:00
Jiacheng Jianggithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Karsa
f53e5bfff0 feat(icons): add house-cog icon (#4904)
* feat(icons): add house-cog icon

* Apply suggestion from @github-actions[bot]

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Apply suggestion from @github-actions[bot]

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Apply suggestion from @github-actions[bot]

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(icons): address house-cog review feedback

* Update icons/house-cog.json

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-24 07:42:30 +02:00
Eric Fennisandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> f06ac67e33 chore(typchecking): More typecheck jobs for all packages (#4885)
* Add typecheck jobs

* update lockfile

* Fix types

* Fix lucide solid types

* Fix type lucide-react-native

* fix: align react-native svg mock children typing

* fix: format build font spec

* fix: restore shared workflow and RN class context

* fix: restore lucide-react build typecheck

* fix: keep react build generating icons

* fix: preserve builder color prop types

* test: cover RN provider class merge

* Revert 711255b

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-22 13:49:09 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b0b1a4f8ab chore(deps): bump the vue-deps group with 3 updates (#4893)
Bumps the vue-deps group with 3 updates: [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/HEAD/packages/plugin-vue), [@vue/test-utils](https://github.com/vuejs/test-utils) and [vue](https://github.com/vuejs/core).


Updates `@vitejs/plugin-vue` from 6.0.8 to 6.0.9
- [Release notes](https://github.com/vitejs/vite-plugin-vue/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-vue/commits/plugin-vue@6.0.9/packages/plugin-vue)

Updates `@vue/test-utils` from 2.5.0 to 2.5.1
- [Release notes](https://github.com/vuejs/test-utils/releases)
- [Commits](https://github.com/vuejs/test-utils/compare/v2.5.0...v2.5.1)

Updates `vue` from 3.5.42 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.42...v3.5.43)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-vue"
  dependency-version: 6.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vue-deps
- dependency-name: "@vue/test-utils"
  dependency-version: 2.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vue-deps
- dependency-name: vue
  dependency-version: 3.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: vue-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 07:54:14 +02:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
3a37bfa5cd chore(deps-dev): bump the angular-deps group across 1 directory with 14 updates (#4895)
* chore(deps-dev): bump the angular-deps group across 1 directory with 14 updates

Bumps the angular-deps group with 14 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `22.2.0` | `22.5.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `22.2.0` | `22.5.0` |
| [@angular/build](https://github.com/angular/angular-cli) | `22.1.7` | `22.1.8` |
| [@angular/cli](https://github.com/angular/angular-cli) | `22.1.7` | `22.1.8` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `22.1.5` | `22.1.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `22.1.5` | `22.1.7` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `22.1.5` | `22.1.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `22.1.5` | `22.1.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `22.1.5` | `22.1.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `22.1.5` | `22.1.7` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `22.1.5` | `22.1.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `22.1.5` | `22.1.7` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.10` | `5.0.1` |
| [angular-eslint](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/angular-eslint) | `22.2.0` | `22.5.0` |



Updates `@angular-eslint/builder` from 22.2.0 to 22.5.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/builder/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.5.0/packages/builder)

Updates `@angular-eslint/schematics` from 22.2.0 to 22.5.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/schematics/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.5.0/packages/schematics)

Updates `@angular/build` from 22.1.7 to 22.1.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.1.7...v22.1.8)

Updates `@angular/cli` from 22.1.7 to 22.1.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v22.1.7...v22.1.8)

Updates `@angular/common` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/common)

Updates `@angular/compiler` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/compiler)

Updates `@angular/compiler-cli` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/compiler-cli)

Updates `@angular/core` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/core)

Updates `@angular/forms` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/forms)

Updates `@angular/platform-browser` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/platform-browser)

Updates `@angular/platform-server` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/platform-server)

Updates `@angular/router` from 22.1.5 to 22.1.7
- [Release notes](https://github.com/angular/angular/releases)
- [Commits](https://github.com/angular/angular/commits/v22.1.7/packages/router)

Updates `@vitest/browser-playwright` from 4.1.10 to 5.0.1
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.1/packages/browser-playwright)

Updates `angular-eslint` from 22.2.0 to 22.5.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/angular-eslint/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v22.5.0/packages/angular-eslint)

---
updated-dependencies:
- dependency-name: "@angular-eslint/builder"
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular-eslint/schematics"
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
- dependency-name: "@angular/build"
  dependency-version: 22.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/cli"
  dependency-version: 22.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/common"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/compiler-cli"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/core"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/forms"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-browser"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/platform-server"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@angular/router"
  dependency-version: 22.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular-deps
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: angular-deps
- dependency-name: angular-eslint
  dependency-version: 22.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: angular-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: keep vitest browser provider on v4

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-22 07:53:16 +02:00
Karsa 16fb55f52c fix(icons): changed mail-pen (#4899)
adjusted the off grid pen element
2026-09-22 07:52:34 +02:00
Jakob GuddasandEric Fennis 951813ce76 fix(icons): changed map-pinned icon (#4880)
* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Updated icons/map-pinned.svg

* Updated icons/map-pinned.json

* Update map-pinned.json

* Update map-pinned.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-19 12:26:19 +02:00
ba6751ac45 feat(site): Better icon detail page and add unreleased flag (#4877)
* setup icon popularity call

* Adjust sort button

* Adjust sorting

* Adds aliases

* Format code

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Add check for env variable

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Improve styling on mobile

* Better icon detail page

* Update detail page

* feat: make edit button more visible

* Fix types

* Add unreleased version tag

* Adjust icon detail

* Added edit button

* format code

* Replace pencil sparkles

* Revert changes

* Fix types

* Add docs checks

* Fix types

* Fix awaiting release

* revert icons changes

* Format code

* Fix type issues

* Update CarbonAdOverlay to not display

Disable CarbonAdOverlay by setting drawerOpen to false.

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

* Fix types

* Fix condition for showing awaiting release icons

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

* Format code

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Guddas <github@jguddas.de>
2026-09-18 14:22:47 +02:00
99c1037550 test(packages/shared): cover buildLucideIconForReact (#4872)
buildLucideIconForReact is the entry point both lucide-react and
lucide-react-native use to build an icon, and its whole job is renaming
the hyphenated SVG attributes to the spellings React expects. Nothing
tested that rename directly: the other build helpers have specs in
packages/icons/tests, and this one had none anywhere in the repo.

The spec pins the five renames it applies, checks that attributes React
already accepts are left alone, that aria-hidden stays hyphenated, that
vector-effect is renamed on child nodes for a non-scaling stroke, and
that a caller's own attributeNames are merged without displacing the
React spellings.

Verified that the spec is not vacuous: removing the class -> className
entry from the rename map fails two of the five tests.

Co-authored-by: Vugar Bakhishov <vugarbbakhishov-hub@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-18 14:08:40 +02:00
Eric Fennis e32a4a2c10 Fix type lucide-vue (#4883) 2026-09-18 12:29:14 +02:00
Oleg Bezrukavnikov 5d9cc1580a fix(packages/react-native): pass testID to the rendered Svg element (#4881)
The Icon component destructured `testID` out of its props and re-emitted it as
`data-testid`, which is a web attribute. react-native-svg only forwards `testID`
to the native view (see extractProps) and drops `data-testid`, so
`<Icon testID="my-icon" />` rendered an element that no React Native test query
or UI automation tool can find. `LucideProps` extends `SvgProps`, so the prop
type checks and the failure is silent.

v0 spread `testID` straight onto `Svg`, so this is a regression for React Native
consumers. An explicitly passed `data-testid` still reaches the element through
the remaining props, so nothing is lost for react-native-web users.

The react-native-svg test mock now maps `testID` onto `data-testid`, so the
DOM based test suite identifies elements the way React Native does. Every
existing `testID` test fails against the previous behaviour.
2026-09-18 11:57:55 +02:00
Nathan De PachtereandEric Fennis d9f72b2525 feat(icons): added line-dot-bottom-vertical icon (#3857)
* Added icons/line-dot-bottom-vertical.svg

* Added icons/line-dot-bottom-vertical.json

* Update line-dot-bottom-vertical.json

* Format code

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 16:30:55 +02:00
Nathan De Pachteregithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
0d0408c43b feat(icons): added line-dot-top-vertical icon (#3856)
* Added icons/line-dot-top-vertical.svg

* Added icons/line-dot-top-vertical.json

* Update icons/line-dot-top-vertical.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Remove unused tags from line-dot-top-vertical.json

Removed unused tags from the line-dot-top-vertical icon.

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

* Update line-dot-top-vertical.json

* format code

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 16:30:01 +02:00
Daniel BayleyandEric Fennis 55d30bda04 feat(icons): tweak card-sim chip (#3649)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 15:21:22 +02:00
Liang XuandLiang Xu f90f10a24a feat(packages): export __iconNode data across framework packages (#4761)
Fixes #4644

Signed-off-by: Liang Xu <lx3133584@users.noreply.github.com>
Co-authored-by: Liang Xu <lx3133584@users.noreply.github.com>
2026-09-17 14:58:48 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> f3e36fb0d7 chore(deps-dev): bump react-native (#4673)
Bumps the react-native-deps group with 1 update in the / directory: [react-native](https://github.com/react/react-native/tree/HEAD/packages/react-native).


Updates `react-native` from 0.76.9 to 0.87.1
- [Release notes](https://github.com/react/react-native/releases)
- [Changelog](https://github.com/react/react-native/blob/main/CHANGELOG-0.7x.md)
- [Commits](https://github.com/react/react-native/commits/v0.87.1/packages/react-native)

---
updated-dependencies:
- dependency-name: react-native
  dependency-version: 0.86.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: react-native-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 14:56:09 +02:00
KarsaandEric Fennis 4faa47c0b0 fix(packages/svelte,solid): fix shared type imports in Solid and Svelte (#4846)
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 14:30:42 +02:00
Nathan De Pachteregithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
e593a44ad2 feat(icons): added line-dot-left-horizontal icon (#3855)
* Added icons/line-dot-left-horizontal.svg

* Added icons/line-dot-left-horizontal.json

* Update icons/line-dot-left-horizontal.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Add use cases to line-dot-left-horizontal.json

Added use cases for line-dot-left-horizontal icon.

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 13:41:06 +02:00
dba8fb7932 feat(icons): added square-sparkles icon (#3610)
* Added icons/square-sparkles.svg

* Added icons/square-sparkles.json

* apply design from @karsa-mistmere

* Add use-cases field to square-sparkles.json

* Add use cases for square sparkles icon

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

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 13:40:38 +02:00
Tylergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Claude Sonnet 5Eric Fennis
a87cecebe3 feat(icons): added briefcase-plus icon (#4757)
* feat(icons): added `briefcase-plus` icon

* Update icons/briefcase-plus.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/briefcase-plus.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/briefcase-plus.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore(lab): remove `briefcase-plus` from lab

Icon has been promoted to icons/, no longer needed in lab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Updated briefcase-plus icon

* Update contributors in briefcase-plus.json

Added 'jguddas' to contributors list in briefcase-plus.json

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-17 09:25:14 +02:00
psj.devgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIPeter Jamrozinski
3b9ea6d087 feat(icons): added square-dashed-plus (#4849)
* feat(icons): added square-dashed-plus

* Update icons/square-dashed-plus.json

Suggested via PR AI

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/square-dashed-plus.json

Suggested by PR AI

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/square-dashed-plus.json

Suggestion made by PR AI

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Revise use cases for square dashed plus icon

AI PR Suggestion

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

---------

Co-authored-by: Peter Jamrozinski <peter.jamrozinski@johnsonfit.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-17 09:21:20 +02:00
Jakob Guddas 076b52527f fix(icons): changed broccoli icon (#4871)
* Updated icons/broccoli.svg

* Updated icons/broccoli.json
2026-09-16 10:18:44 +02:00
Karsa 58ef6830c6 fix(icons): changed calendar-chevrons-right (#4865) 2026-09-14 21:04:50 +02:00
HPRILLER f032bed22e fix(icons): remove clipped path from save-off (#4848) 2026-09-14 19:58:53 +02:00
conanClaude Opus 5Copilot Autofix powered by AIgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric FennisKarsa
266fd1c717 feat(icons): add rotate-cw-clock icon (#3979)
* Added icons/update.svg

* Added icons/update.json

* Updated icons/update.json

* chore(icons): rename update to update-clockwise

* refactor(icons): rename `update-clockwise` to `rotate-cw-clock`

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add use cases for rotate-cw-clock icon

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

* Update icons/rotate-cw-clock.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Refine use-case descriptions in rotate-cw-clock.json

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

* Potential fix for pull request finding

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

* Update rotate-cw-clock.json

* Update rotate-cw-clock.svg

* Update rotate-cw-clock.json

* Update rotate-cw-clock.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:55:42 +02:00
Ethan Hazelgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Eric FennisKarsa
357a50175e feat(icons): added square-dashed-x icon (#4535)
* feat(icons): added square-dashed-x icon

* Update icons/square-dashed-x.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(icons): split square-dashed-x icon into two designs

* Update square-dashed-x.svg

* Update square-dashed-x-corner.svg

* Update square-dashed-x.json

* Update square-dashed-x-corner.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:49:49 +02:00
a50c54cfff feat(icons): Added cupcake icon (#3000)
* Added svg and json for cupcake icon

* Update cupcake.svg

* Update cupcake.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:45:48 +02:00
Alec Rust bd95db1af8 feat(icons): added tube-lotion icon (#4029)
* Added icons/sunscreen.svg

* Added icons/sunscreen.json

* fix(icons): apply reviewed tube-lotion design and metadata

* fix(icons): correct tube-lotion contributor attribution
2026-09-14 11:39:55 +02:00
Saraj Dhakalgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
6b474e16d4 feat(icons): added 'nepali-rupee' icon (#4608)
* Add Nepali Rupee icon

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor: center nepali rupee icon and use perfect circular arc

* Updated with horizontally centered

* Update use cases for Nepali rupee

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

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-14 11:36:57 +02:00
Karsa 566032a95d feat(icons): added door-closed-package icon (#4814)
* Added icons/door-closed-package.svg

* Added icons/door-closed-package.json
2026-09-14 11:31:33 +02:00
Karsa 042bf77119 feat(icons): delegated faucet icon from lab (#4764) 2026-09-14 11:30:28 +02:00
Ubaid UllahandKarsa 5b8975c9a3 feat(icons): add lambda icon (#4017)
* feat(icons): added lambda icon

* fix(icons/lambda): fix linting issues

* feat(icons/lambda): clean up metadata

---------

Co-authored-by: Karsa <contact@karsa.org>
2026-09-14 11:17:11 +02:00
Karsa 7eb8afe2f8 feat(icons): added monitor-pc icon (#4791)
* Added icons/monitor-pc.svg

* Added icons/monitor-pc.json
2026-09-14 11:15:28 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 08b2fd2455 chore(deps-dev): bump astro from 7.3.0 to 7.3.2 in the astro-deps group (#4860)
Bumps the astro-deps group with 1 update: [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro).


Updates `astro` from 7.3.0 to 7.3.2
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@7.3.2/packages/astro)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 7.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: astro-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-14 08:35:43 +02:00
a79b2d131d feat(icons): Added new icons: clef-g, clef-f and clef-c (#4083)
* Added new icons: clef-g, clef-f and clef-c

* rename and design updates from feedback

* fix typo in filename

* Update clef-bass.svg

* Update clef-treble.json

* Update clef-bass.json

* Update clef-alto.json

---------

Co-authored-by: Karsa <contact@karsa.org>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-11 13:03:10 +02:00
Dixgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
b998e2892b feat(icons): added globe-code icon (#3722)
* Added icons/world-wide-web.svg

* Added icons/world-wide-web.json

* Update icons/world-wide-web.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Rename files

* Add 'use-cases' field to globe-code.json

* Add use cases for globe code icon

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

* Fix JSON formatting in globe-code.json

* Add 'development' category to globe-code.json

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

* Update SVG path for globe code icon

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-11 12:43:00 +02:00
Eric FennisandCopilot Autofix powered by AI 5da3ee357d ci(ci.yml): Add dispatch post release (#4847)
* Add dispatch post release

* Refine post-release automation conditions

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-11 12:42:26 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6bbe5ddb07 chore(deps-dev): bump vitest in /integrations/lucide-react/vite (#4844)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.10 to 4.1.11.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 09:26:37 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4a26fa58e0 chore(deps-dev): bump vitest from 4.1.10 to 4.1.11 (#4845)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.10 to 4.1.11.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 09:26:09 +02:00
Karsa 5d592a96aa fix(packages/vue): fix generated icon declaration types for @lucide/vue (#4841) 2026-09-10 22:35:01 +02:00
Karsagithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Claude Opus 5Eric Fennis
bdf795a8e3 feat(lab): add hand-gear icon (#2327)
* feat(icons): add hand-gear icon

* fix(icons): add danielbayley as co-contributor (hand design)

* fix(icons): fix gear centering

* Add empty use-cases field to hand-gear.json

* Update icons/hand-gear.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update icons/hand-gear.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(lab): moved `hand-gear` icon to lab

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:45:49 +02:00
KarsaandEric Fennis e4be16acd5 feat(docs): added "How to use Lucide icons" section to resources (#4829)
* fix(docs): replace missing LucideIcon icon names in guides

* feat(docs): add How to use Lucide icons section to resources

* feat(docs): update accessibility guide links

* fix(docs): fix dead link in guide.md

* fix(docs): fix color in example image

* fix(docs): fix overlay color in example images

* fix(how-to): added caution guidance type & rework some guidance rules relating to compact interfaces

* fix(how-to): replaced caution items, what the heck, when did I reorder these 😅

* fix(how-to): fix colors

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2026-09-10 16:07:18 +02:00
Karsa ea1242d384 fix(icons): also fix album icon in HomeHeroIconsCard.data.ts (#4839) 2026-09-10 15:34:39 +02:00
Karsa 7cf805a408 fix(icons): remove duplicate use-cases prop from cookie.json (#4838) 2026-09-10 15:28:15 +02:00
Karsa c2c28580b3 fix(icons): changed cookie icon (#4815)
* Updated icons/cookie.svg

* Updated icons/cookie.json

* Updated icons/cookie.svg

* Updated icons/cookie.json

* Updated icons/cookie.json
2026-09-10 12:47:06 +02:00
vuong quang huygithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIJakob GuddasEric Fennis
858b662775 feat(icons): added plant-pot icon (#3122)
* Added icons/plant-pot.svg

* Added icons/plant-pot.json

* Updated icons/plant-pot.svg

* Updated icons/plant-pot.svg

* Apply batched suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Add use-cases field to plant-pot.json

Added an empty array for use-cases in the plant-pot icon JSON.

* Update plant-pot.svg with new design paths from @karsamistmere

* Add contributor 'karsa-mistmere' to plant-pot.json

* Add use cases for plant pot icon

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

---------

Co-authored-by: Jakob Guddas <github@jguddas.de>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-10 12:22:43 +02:00
c17f928577 feat(icons): add messages-circle icon (#4754)
* feat: add messages-circle icon

* chore: apply bot metadata suggestions

* fix: update messages-circle to compliant design and add contributors

* Revise use cases for messaging icon

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

---------

Co-authored-by: Mirazstudio-offical <miraza@example.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-10 12:21:47 +02:00
Elena Kovelskikhgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot Autofix powered by AIEric Fennis
d32ef89ef9 feat(icons): added notebook-dot icon (#3228)
* Added icons/notebook-dot.svg

* Added icons/notebook-dot.json

* Update notebook-dot.svg

* Apply suggestion from @github-actions[bot]

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Add use-cases field to notebook-dot.json

* Apply batched suggestions from code review

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

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-10 12:17:44 +02:00
5fb5967884 feat(icons): added houses icon (#3241)
* feat(icons): added `houses` icon

* Update icons/houses.svg

* Add contributor 'jguddas' to houses.json

* Add use cases for houses icon

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

* Remove houses from lab

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-10 12:17:21 +02:00
Mo 1cb69f6f9c fix(icons): correct misspelled tags (#4836) 2026-09-10 11:10:12 +02:00
288 changed files with 5560 additions and 3241 deletions
+29
View File
@@ -64,6 +64,7 @@ jobs:
contents: write # Required to create the release and tag
outputs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
RELEASE_URL: ${{ steps.create-release.outputs.url }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
@@ -102,6 +103,7 @@ jobs:
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
- name: Create Release
id: create-release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
@@ -129,3 +131,30 @@ jobs:
with:
version: ${{ inputs.version }}
tag: ${{ inputs.tag }}
post-release:
name: Trigger post-release automation
if: >-
always() &&
github.repository == 'lucide-icons/lucide' &&
(needs.start-release.result == 'success' || needs.dispatch-release.result == 'success')
needs: [create-release, start-release, dispatch-release]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Dispatch post_release event
env:
GH_TOKEN: ${{ secrets.POST_RELEASE_TOKEN }}
RELEASE_TAG: ${{ needs.create-release.outputs.VERSION || inputs.version }}
RELEASE_URL: ${{ needs.create-release.outputs.RELEASE_URL }}
run: |
if [ -z "$RELEASE_URL" ]; then
RELEASE_URL="https://github.com/lucide-icons/lucide/releases/tag/$RELEASE_TAG"
fi
gh api \
--method POST \
repos/lucide-icons/post-release/dispatches \
--field event_type=post_release \
--field "client_payload[release_tag]=$RELEASE_TAG" \
--field "client_payload[release_url]=$RELEASE_URL"
+55
View File
@@ -0,0 +1,55 @@
name: Docs Checks
on:
pull_request:
paths:
- docs/**
- .github/workflows/docs.yml
- package.json
- pnpm-workspace.yaml
- pnpm-lock.yaml
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build docs
run: pnpm --filter @lucide/docs build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate docs data
run: pnpm --filter @lucide/docs run "/^prebuild:.*/"
- name: Typecheck
run: pnpm --filter @lucide/docs typecheck
+16
View File
@@ -46,3 +46,19 @@ jobs:
- name: Test
run: pnpm --filter @lucide/astro test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/astro typecheck
+16
View File
@@ -37,3 +37,19 @@ jobs:
with:
name: lucide-font
path: lucide-font
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter build-font typecheck
+16
View File
@@ -29,3 +29,19 @@ jobs:
- name: Build
run: pnpm --filter @lucide/lab build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/lab typecheck
+16
View File
@@ -31,3 +31,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-preact test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-preact typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-react-native test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-react-native typecheck
+16
View File
@@ -70,3 +70,19 @@ jobs:
- name: Run integration checks
run: pnpm test:integrations:react
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-react typecheck
+17 -1
View File
@@ -27,4 +27,20 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter lucide-react test
run: pnpm --filter @lucide/shared test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/shared typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter lucide-solid test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-solid typecheck
+16
View File
@@ -26,3 +26,19 @@ jobs:
- name: Build
run: pnpm --filter lucide-static build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide-static typecheck
+1 -1
View File
@@ -61,5 +61,5 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
- name: Typecheck
run: pnpm --filter @lucide/svelte typecheck
+16
View File
@@ -47,3 +47,19 @@ jobs:
- name: Test
run: pnpm --filter @lucide/vue test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/vue typecheck
+16
View File
@@ -46,3 +46,19 @@ jobs:
- name: Test
run: pnpm --filter lucide test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter lucide typecheck
+1
View File
@@ -1,6 +1,7 @@
.DS_Store
.idea
.next
.astro
.obsidian
.now
.cache
+1 -2
View File
@@ -1,13 +1,12 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes/index.ts';
import { IconNodeWithKeys } from '../../theme/types';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
+1 -2
View File
@@ -1,6 +1,5 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types';
export default eventHandler((event) => {
const query = getQuery(event);
@@ -19,7 +18,7 @@ export default eventHandler((event) => {
return [name, iconNode];
}
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
+2 -1
View File
@@ -97,7 +97,8 @@ export default defineConfig({
{
text: 'Resources',
items: [
...resourcesSidebar[0].items,
...resourcesSidebar[0].items.map(({ text, link }) => ({ text, link })),
{ text: 'How to use icons', link: '/how-to/' },
{ text: 'Contributing icons', link: '/contribute/icons/' },
],
},
+2 -3
View File
@@ -1,4 +1,3 @@
import { SVGProps } from 'react';
import { getCommands } from './utils';
export type Point = { x: number; y: number };
@@ -15,8 +14,8 @@ export type Path = {
};
export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
RequiredProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
+1 -4
View File
@@ -53,10 +53,7 @@ export const getCommands = (src: string) =>
getNodes(src)
.map(convertToPathNode)
.flatMap(({ d, name }, idx) =>
new SVGPathData(d)
.toAbs()
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
);
const getPaths = (src: string) => {
+3 -3
View File
@@ -4,7 +4,7 @@ import { Category, IconEntity } from '../theme/types';
const directory = path.join(process.cwd(), '../categories');
export function getAllCategoryFiles(): Category[] {
export function getAllCategoryFiles(): Omit<Category, 'iconCount'>[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => {
@@ -21,9 +21,9 @@ export function getAllCategoryFiles(): Category[] {
}
export function mapCategoryIconCount(
categories: Category[],
categories: Omit<Category, 'iconCount'>[],
icons: { categories: IconEntity['categories'] }[],
) {
): Category[] {
return categories.map((category) => ({
...category,
iconCount: icons.reduce(
+2 -1
View File
@@ -1,4 +1,5 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react';
import React from 'react';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server';
+2
View File
@@ -20,6 +20,8 @@ export async function getData(name: string) {
const iconNode = iconNodes[name];
const releaseData = releaseMeta?.[name] ?? {
fromFork: false,
awaitingRelease: false,
createdRelease: {
version: '0.0.0',
date: DATE_OF_FORK,
+25 -11
View File
@@ -1,10 +1,20 @@
import { readFileSync } from 'node:fs';
import { dirname, isAbsolute, resolve } from 'node:path';
import type MarkdownIt from 'markdown-it';
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
import type { MarkdownIt, RenderRule } from './types';
import container from 'markdown-it-container';
type ContainerArgs = [typeof container, string, { render: RenderRule }];
type GuidanceType = 'do' | 'dont' | 'caution';
const isGuidanceOpenToken = (type: string) =>
type === 'container_do_open' ||
type === 'container_dont_open' ||
type === 'container_caution_open';
const isGuidanceCloseToken = (type: string) =>
type === 'container_do_close' ||
type === 'container_dont_close' ||
type === 'container_caution_close';
const parseInfo = (info: string, marker: string) => info.trim().slice(marker.length).trim();
@@ -81,12 +91,12 @@ const hasTopLevelContent = (tokens: Parameters<RenderRule>[0], index: number) =>
return false;
}
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
if (isGuidanceOpenToken(token.type)) {
guidanceDepth += 1;
continue;
}
if (token.type === 'container_do_close' || token.type === 'container_dont_close') {
if (isGuidanceCloseToken(token.type)) {
guidanceDepth -= 1;
continue;
}
@@ -107,7 +117,7 @@ const hasGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
return false;
}
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
if (isGuidanceOpenToken(token.type)) {
return true;
}
}
@@ -123,7 +133,7 @@ const isFirstGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
return true;
}
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
if (isGuidanceOpenToken(token.type)) {
return false;
}
}
@@ -141,6 +151,12 @@ const getExampleOpenIndex = (tokens: Parameters<RenderRule>[0], index: number) =
return index;
};
const labels: Record<GuidanceType, string> = {
do: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`,
dont: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`,
caution: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-exclamation-point"><path d="M12 20 L12 20" /><path d="M12 4 L12 15" /></svg> Caution`,
};
export default function examplePlugin(md: MarkdownIt) {
const escapeHtml = md.utils.escapeHtml;
@@ -170,7 +186,7 @@ export default function examplePlugin(md: MarkdownIt) {
},
];
const createGuidanceContainer = (type: 'do' | 'dont'): ContainerArgs => [
const createGuidanceContainer = (type: GuidanceType): ContainerArgs => [
container,
type,
{
@@ -185,10 +201,7 @@ export default function examplePlugin(md: MarkdownIt) {
hasTopLevelContent(tokens, exampleOpenIndex) && isFirstGuidance(tokens, idx)
? '</div>\n'
: '';
const label =
type === 'do'
? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`
: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`;
const label = labels[type];
return `${contentClose}<figure class="example-guidance example-guidance--${type}">${media}<figcaption class="example-guidance__caption"><div class="example-guidance__label">${label}</div>\n`;
}
@@ -201,4 +214,5 @@ export default function examplePlugin(md: MarkdownIt) {
md.use(...createExampleContainer());
md.use(...createGuidanceContainer('do'));
md.use(...createGuidanceContainer('dont'));
md.use(...createGuidanceContainer('caution'));
}
+1 -3
View File
@@ -1,6 +1,4 @@
import type MarkdownIt from 'markdown-it';
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
import type Token from 'markdown-it/lib/token.mjs';
import type { MarkdownIt, RenderRule, Token } from './types';
import container from 'markdown-it-container';
import sandpackTheme from '../theme/sandpackTheme.json';
+1 -1
View File
@@ -16,7 +16,7 @@
* />
*/
import type MarkdownIt from 'markdown-it';
import type { MarkdownIt } from './types';
type SnackParams = Record<string, string>;
+7
View File
@@ -0,0 +1,7 @@
import type { MarkdownRenderer } from 'vitepress';
// VitePress 1.x renders with markdown-it 14, so derive the plugin types from its renderer
// instead of the markdown-it 15 package installed in docs.
export type MarkdownIt = MarkdownRenderer;
export type RenderRule = NonNullable<MarkdownIt['renderer']['rules'][string]>;
export type Token = Parameters<RenderRule>[0][number];
+1 -1
View File
@@ -94,7 +94,7 @@ export const angularSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -69,7 +69,7 @@ export const astroSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1
View File
@@ -36,6 +36,7 @@ const sidebar: Sidebar = {
'/guide/react-native/': reactNativeSidebar,
'/guide/angular/': angularSidebar,
'/guide/static/': lucideStaticSidebar,
'/how-to': resourcesSidebar,
'/community': resourcesSidebar,
'/contribute': resourcesSidebar,
'/license': resourcesSidebar,
+1 -1
View File
@@ -79,7 +79,7 @@ export const lucideSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
],
+1 -1
View File
@@ -89,7 +89,7 @@ export const preactSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -100,7 +100,7 @@ export const reactSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+19
View File
@@ -29,6 +29,25 @@ export const resourcesSidebar = [
},
],
},
{
text: 'How to use Lucide icons',
items: [
{
text: 'Getting started',
link: '/how-to/',
},
{
text: 'Accessibility guide',
link: '/how-to/accessibility',
desc: 'Learn how to make icons readable, understandable, and usable with assistive technology.',
},
{
text: 'How to pick the right icon',
link: '/how-to/icon-guide',
desc: 'Learn how to decide when icons belong in an interface and how to pick symbols people are likely to recognize.',
},
],
},
{
text: 'Contributing icons',
items: [
+1 -1
View File
@@ -90,7 +90,7 @@ export const solidSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -75,7 +75,7 @@ export const lucideStaticSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -79,7 +79,7 @@ export const svelteSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -85,7 +85,7 @@ export const vueSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
link: '/how-to/accessibility',
desc: 'Accessibility best practices',
},
{
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useLocalStorage } from '@vueuse/core';
import IconButton from './base/IconButton.vue';
import Button from './base/Button.vue';
import { x } from '../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import { computed, onMounted, ref } from 'vue';
@@ -65,16 +65,17 @@ function hidePopup() {
}"
class="ask-overlay"
>
<IconButton
<Button
@click="hidePopup"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</IconButton>
</Button>
<h2 class="title">
We value your feedback!
@@ -1,7 +1,38 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
href?: string;
}>()
const isExternal = computed(() => {
return props.href?.startsWith('http') ?? false
})
const component = computed(() => {
return props.href ? 'a' : 'button'
})
const target = computed(() => {
return isExternal.value ? '_blank' : undefined
})
const rel = computed(() => {
return isExternal.value ? 'noreferrer noopener' : undefined
})
</script>
<template>
<button v-bind="$attrs" class="icon-button">
<component
:is="component"
v-bind="$attrs"
:href="href"
:target="target"
:rel="rel"
class="icon-button"
>
<slot />
</button>
</component>
</template>
<style scoped>
@@ -16,12 +47,21 @@
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
color: inherit;
text-decoration: none;
/* width: 56px;
height: 56px; */
font-size: 24px;
gap: 8px;
font-size: 16px;
font-weight: 500;
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
}
.icon-button span {
padding: 0 4px;
display: block;
}
.icon-button:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
@@ -14,7 +14,7 @@ export interface InputProps {
import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from '@lucide/vue/src/Icon';
import { x } from '../../../data/iconNodes';
import IconButton from './IconButton.vue';
import Button from './Button.vue';
const props = withDefaults(defineProps<InputProps>(), {
type: 'text',
@@ -68,9 +68,9 @@ defineExpose({
ref="input"
:value="modelValue"
v-bind="$attrs"
@input="$emit('update:modelValue', $event.target.value)"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
<IconButton
<Button
@click="onClear"
v-if="type === 'search' && modelValue"
class="clear-button"
@@ -79,9 +79,10 @@ defineExpose({
>
<Icon
:iconNode="x"
name="x"
:size="20"
/>
</IconButton>
</Button>
<kbd
v-if="shortcut"
class="shortcut"
@@ -46,6 +46,7 @@ const value = computed({
<template #icon>
<Icon
:iconNode="search"
name="search"
class="search-icon"
/>
</template>
@@ -9,7 +9,8 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
<p>
<Icon
:iconNode="RocketDuotoneIcon"
size="18"
name="rocket-duotone"
:size="18"
class="rocket-icon"
/>
<VPLink
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
const props = defineProps<{
tabs: string[];
}>();
</script>
<template>
<TabGroup>
<TabList class="tab-list">
<template v-for="(tab, index) in props.tabs" :key="index">
<Tab :id="`tab-${index}`" class="tab">{{ tab }}</Tab>
</template>
</TabList>
<TabPanels>
<template v-for="(_, name) in $slots">
<TabPanel>
<slot :name="name" />
</TabPanel>
</template>
</TabPanels>
</TabGroup>
</template>
<style lang="css" scoped>
.tab-list {
display: flex;
gap: 8px;
margin-bottom: 32px;
border-bottom: 1px solid var(--vp-c-divider);
justify-content: center;
}
.tab {
display: block;
line-height: 32px;
font-size: 16px;
padding: 10px 12px;
font-weight: 500;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
border-bottom: 2px solid transparent;
}
.tab[aria-selected="true"] {
color: var(--vp-c-text-1);
transition: color 0.25s;
border-bottom: 2px solid var(--vp-c-brand);
}
@media (min-width: 860px) {
.tab-list {
justify-content: flex-start;
}
.tab {
font-size: 18px;
padding: 10px 16px;
}
}
@media (min-width: 960px) {
.tab {
}
}
@media (min-width: 1152px) {
.tab {
}
}
</style>
@@ -34,7 +34,7 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
type="range"
v-bind="$attrs"
v-bind:value="modelValue"
v-on:input="$emit('update:modelValue', Number($event.target.value))"
v-on:input="$emit('update:modelValue', Number(($event.target as HTMLInputElement).value))"
:min="min"
:max="max"
:step="step"
@@ -1,16 +1,17 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import IconButton from './IconButton.vue';
import Button from './Button.vue';
</script>
<template>
<IconButton class="reset-button">
<Button class="reset-button">
<Icon
:size="20"
:iconNode="rotateCw"
name="rotate-cw"
/>
</IconButton>
</Button>
</template>
<style scoped>
@@ -27,6 +27,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
<slot name="end-icon">
<Icon
:iconNode="chevronsUpDown"
name="chevrons-up-down"
class="chevron-icon"
aria-hidden="true"
/>
@@ -57,6 +58,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
>
<Icon
:iconNode="check"
name="check"
class="check"
aria-hidden="true"
/>
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { type FunctionalComponent } from 'vue';
import { type LucideProps } from '@lucide/vue';
import { type LucideProps } from '@lucide/vue/src/types';
import { Sandpack } from 'sandpack-vue3';
defineProps<FunctionalComponent<LucideProps>>();
@@ -10,7 +10,14 @@ import { useLocalStorage } from '@vueuse/core';
const { page } = useData();
const router = useRouter();
const frameworks = [
interface Framework {
name: string;
icon: string;
iconDark?: string;
value: string;
}
const frameworks: Framework[] = [
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
@@ -48,7 +55,7 @@ const selected = computed(() => {
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
});
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
function onSelectFramework(item: Framework) {
fallbackFramework.value = item;
if (item.value !== router.route.path) {
const likeRoute = router.route.path.replace(selected.value.value, item.value);
@@ -2,9 +2,12 @@
import { ref, shallowRef } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import { useRouter } from 'vitepress';
import { motion, Variants, useScroll, useTransform } from 'motion-v';
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
import LucideIcon from '../base/LucideIcon.vue';
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
type Variants = MotionOptions['variants'];
const emit = defineEmits(['animation-complete']);
const MotionLucideIcon = motion.create(LucideIcon);
@@ -129,7 +132,7 @@ const onAnimationComplete = (item) => {
const randomIndex = ref(Math.floor(Math.random() * 64));
const iconAnimationVariants = {
const iconAnimationVariants: Variants = {
initial: {
animationName: 'end',
opacity: 0,
@@ -149,7 +152,7 @@ const iconAnimationVariants = {
const shrinkIconAnimation = ref('initial');
const shrinkIconVariants = {
const shrinkIconVariants: Variants = {
initial: { strokeWidth: 2 },
shrinkIcons: (index) => ({
animationName: 'shrinkIcons',
@@ -18,7 +18,7 @@ const pickIcons = [
'clapperboard',
'tool-case',
'printer',
'album',
'square-bookmark',
'pencil-ruler',
'calendar',
'brain',
@@ -69,7 +69,7 @@ const pickIcons = [
'stethoscope',
];
export default {
const loader = {
async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
@@ -96,3 +96,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -1,4 +1,4 @@
export default {
const loader = {
async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
.then((res) => {
@@ -16,3 +16,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -15,10 +15,11 @@ import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
class="badge-special confetti-button animate"
href="/guide/version-1"
>
<Icon :iconNode="RocketDuotoneIcon" />
<Icon :iconNode="RocketDuotoneIcon" name="rocket-duotone" />
Version 1
<Icon
:iconNode="moveRight"
name="move-right"
class=""
/>
</Badge>
@@ -41,8 +41,8 @@ const sizeCssVar = useCssVar(
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
syncRef(strokeWidth, strokeWidthCssVar, { transform: { ltr: String, rtl: Number } })
syncRef(size, sizeCssVar, { transform: { ltr: String, rtl: Number } })
function resetStyle () {
color.value = 'currentColor'
@@ -1,4 +1,4 @@
export default {
const loader = {
async load() {
return {
packages: [
@@ -61,3 +61,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { computed } from 'vue';
const props = defineProps<{
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
@@ -1,4 +1,6 @@
const RocketDuotoneIcon = [
import type { LucideIconNode } from '@lucide/vue/src/types';
const RocketDuotoneIcon: LucideIconNode[] = [
[
'path',
{
@@ -16,6 +18,6 @@ const RocketDuotoneIcon = [
'clip-rule': 'evenodd',
},
],
] as const;
];
export default RocketDuotoneIcon;
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import Button from '../base/Button.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
import { x } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
@@ -40,16 +40,17 @@ onMounted(() => {
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton
<Button
@click="hideAd"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</IconButton>
</Button>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
</div>
</template>
@@ -1,14 +1,17 @@
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
export default {
const loader = {
async load() {
let categories = getAllCategoryFiles();
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
const categories = mapCategoryIconCount(getAllCategoryFiles(), Object.values(iconsMetaData));
return {
categories,
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { ref, computed, watch } from 'vue'
import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared'
import { isActive } from 'vitepress/dist/client/shared.js'
import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue'
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { pencilSparkles } from '../../../data/iconNodes';
import { IconEntity } from '../../types';
import getStudioLink from '../../utils/getStudioLink';
import Button from '../base/Button.vue';
import Icon from '@lucide/vue/src/Icon.ts';
defineProps<{
icon: IconEntity
}>()
</script>
<template>
<Button
:href="getStudioLink(icon.name, icon.iconNode, 'icon-info')"
target="_blank"
rel="noopener noreferrer"
>
<Icon :iconNode="pencilSparkles" name="pencil-sparkles" :size="24" />
<span class="text">Edit in studio</span>
</Button>
</template>
<style scoped>
.text {
margin: 0 4px;
}
</style>
@@ -30,10 +30,15 @@ const props = defineProps<{
.contributors {
display: flex;
flex-direction: row;
align-items: center;
align-items: start;
/* justify-content: flex-end; */
gap: 16px;
gap: 2px;
}
.contributors .label {
margin-top: 12px;
}
.avatar-group {
display: flex;
}
@@ -16,19 +16,17 @@ const tags = computed(() => {
</script>
<template>
<!-- <PageContainer class="overview"> -->
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
<!-- </PageContainer> -->
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
</template>
<style scoped>
@@ -6,7 +6,11 @@ import useConfetti from '../../composables/useConfetti';
const { animate, confetti } = useConfetti();
const slots = useSlots();
const copiedText = computed(() => slots.default?.()[0].children);
const copiedText = computed(() => {
const children = slots.default?.()[0].children;
return typeof children === 'string' ? children : '';
});
function copyText() {
navigator.clipboard.writeText(copiedText.value);
@@ -25,6 +29,7 @@ function copyText() {
<slot />
<Icon
:iconNode="copy"
name="copy"
:size="20"
class="copy-icon"
/>
@@ -2,7 +2,7 @@
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import IconButton from '../base/IconButton.vue';
import Button from '../base/Button.vue';
import IconContributors from './IconContributors.vue';
import IconPreview from './IconPreview.vue';
import { x, expand } from '../../../data/iconNodes';
@@ -12,6 +12,7 @@ import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
import EditInStudioButton from './EditInStudioButton.vue';
const props = defineProps<{
iconName: string | null;
@@ -70,12 +71,19 @@ const Expand = createLucideIcon('Expand', expand);
<div class="overlay-panel">
<nav class="overlay-menu">
<Badge
v-if="icon.createdRelease"
v-if="icon.awaitingRelease"
class="version awaiting-release"
>Unreleased</Badge
>
<Badge
v-else-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
>v{{ icon.createdRelease.version }}</Badge
>
<IconButton
{{ icon.createdRelease.version }}
</Badge>
<EditInStudioButton :icon="icon"/>
<Button
@click="
go(
icon.externalLibrary
@@ -85,10 +93,10 @@ const Expand = createLucideIcon('Expand', expand);
"
>
<component :is="Expand" />
</IconButton>
<IconButton @click="onClose">
</Button>
<Button @click="onClose">
<component :is="CloseIcon" />
</IconButton>
</Button>
</nav>
<IconPreview
id="previewer"
@@ -200,7 +208,14 @@ const Expand = createLucideIcon('Expand', expand);
margin-right: 24px;
}
.version.awaiting-release {
color: var(--vp-c-danger-3);
}
.contributors {
justify-content: flex-end;
position: absolute;
bottom: 24px;
right: 24px;
}
</style>
@@ -10,30 +10,36 @@ import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes';
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
import type { IconDeprecationReason } from '../../../../../tools/build-icons/types.ts';
import IconTags from "./IconTags.vue"
import getStudioLink from '../../utils/getStudioLink';
const props = defineProps<{
icon: IconEntity;
popoverPosition?: 'top' | 'bottom';
showEditButton?: boolean;
}>();
const { go } = useRouter();
const { page } = useData();
const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return [];
return props.icon.tags.join(' • ');
});
const DiamondIcon = createLucideIcon('Diamond', diamond);
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
return deprecationReasonTemplate(props.icon.deprecationReason, {
return deprecationReasonTemplate(props.icon.deprecationReason as IconDeprecationReason, {
componentName: props.icon.name,
iconName: props.icon.name,
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
});
});
const iconPath = computed(() => {
if (props.icon.externalLibrary) {
return `icons/${props.icon.externalLibrary}/${props.icon.name}`;
}
return `icons/${props.icon.name}`;
});
</script>
<template>
@@ -60,14 +66,7 @@ const deprecatedTitle = computed(() => {
Deprecated
</Badge>
</div>
<div
class="tags-scroller"
v-if="tags.length"
>
<p class="icon-tags horizontal-scroller">
{{ tags }}
</p>
</div>
<IconTags :tags="props.icon.tags" />
<div class="group">
<Badge
v-for="category in icon.categories"
@@ -80,26 +79,10 @@ const deprecatedTitle = computed(() => {
<div class="group buttons">
<VPButton
v-if="
!page?.relativePath?.startsWith?.(
icon.externalLibrary
? `icons/${icon.externalLibrary}/${icon.name}`
: `icons/${icon.name}`,
)
"
:href="
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`
"
v-if="!page?.relativePath?.startsWith?.(iconPath)"
:href="iconPath"
text="See in action"
@click="
go(
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`,
)
"
@click="go(iconPath)"
/>
<CopySVGButton
:name="icon.name"
@@ -115,6 +98,38 @@ const deprecatedTitle = computed(() => {
</template>
<style scoped>
.studio-edit-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 14px;
min-height: 40px;
border: 1px solid var(--vp-button-alt-border);
border-radius: 999px;
background-color: var(--vp-button-alt-bg);
color: var(--vp-button-alt-text);
font-size: 15px;
font-weight: 600;
line-height: 1;
text-decoration: none;
transition:
border-color 0.2s,
color 0.2s,
background-color 0.2s,
transform 0.2s;
margin-left: 0;
margin-inline-start: 0;
}
.studio-edit-button:hover,
.studio-edit-button:focus-visible {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
transform: translateY(-1px);
}
.group {
display: flex;
flex-wrap: wrap;
@@ -156,66 +171,6 @@ const deprecatedTitle = computed(() => {
background-color: var(--vp-c-brand-2);
}
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
white-space: nowrap;
position: absolute;
top: 0;
left: 0;
right: 0;
margin-top: 0;
margin-bottom: 0;
}
.tags-scroller {
position: relative;
max-width: 100%;
width: 100%;
height: 28px;
padding: 8px 0 16px;
margin-bottom: 16px;
margin-top: 8px;
align-items: center;
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv));
}
.horizontal-scroller {
overflow-x: scroll;
/* Hide Scrollbar */
-ms-overflow-style: none;
scrollbar-width: none;
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
}
.horizontal-scroller::-webkit-scrollbar {
width: 0;
display: none;
}
.horizontal-scroller::-webkit-scrollbar-track {
background: transparent;
}
.horizontal-scroller::-webkit-scrollbar-thumb {
background: transparent;
border: none;
}
.tags-scroller::after {
content: '';
position: absolute;
bottom: 0;
width: 32px;
height: 100%;
/* Background Gradient left to right */
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--gradient-background) 100%);
right: 0;
pointer-events: none;
}
.buttons {
margin-top: 24px;
}
@@ -3,7 +3,6 @@ import type { IconEntity } from '../../types';
import { computed, ref } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useIconStyleContext } from '../../composables/useIconStyle';
import { squarePen } from '../../../data/iconNodes';
const props = defineProps<{
name: IconEntity['name'];
@@ -11,6 +10,8 @@ const props = defineProps<{
customizable?: boolean;
}>();
const activeSize = ref('1000%');
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const previewIcon = ref();
@@ -20,31 +21,6 @@ const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
const SquarePenIcon = createLucideIcon('SquarePen', squarePen);
const innerSvg = computed(() => {
if (!props.iconNode) return '';
const string = props.iconNode
.map(([tag, attrs]) => {
const attributes = Object.entries(attrs)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return `<${tag} ${attributes}/>`;
})
.join('');
return string
.replace(/>[\r\n ]+</g, '><')
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
.trim();
});
const base64InnerSvg = computed(() => {
if (typeof window === 'undefined') return '';
return btoa(innerSvg.value);
});
</script>
<template>
@@ -55,6 +31,8 @@ const base64InnerSvg = computed(() => {
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
class="preview-icon"
:data-size="activeSize"
/>
<svg
class="icon-grid"
@@ -62,6 +40,7 @@ const base64InnerSvg = computed(() => {
fill="none"
stroke-width="0.1"
xmlns="http://www.w3.org/2000/svg"
:class="{ show: activeSize === '1000%' }"
>
<g
:key="`grid-${i}`"
@@ -83,16 +62,17 @@ const base64InnerSvg = computed(() => {
/>
</g>
</svg>
<a
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=icon-detail-preview`"
target="_blank"
rel="noopener noreferrer"
class="studio-button"
title="Open in Lucide Studio"
aria-label="Open in Lucide Studio"
>
<SquarePenIcon :size="14" />
</a>
<div class="size-buttons" :class="{ show: activeSize !== '1000%' }">
<button class="size-button" @click="activeSize = '1000%'" :class="{ active: activeSize === '1000%' }">
1000%
</button>
<button class="size-button" @click="activeSize = '200%'" :class="{ active: activeSize === '200%' }">
200%
</button>
<button class="size-button" @click="activeSize = '100%'" :class="{ active: activeSize === '100%' }">
100%
</button>
</div>
</div>
</template>
@@ -104,56 +84,87 @@ const base64InnerSvg = computed(() => {
top: 0;
left: 0;
stroke: var(--vp-c-divider);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.icon-grid.show {
opacity: 1;
}
.icon-container {
height: 100%;
aspect-ratio: 1/1;
position: relative;
background: var(--vp-c-bg-alt);
border-radius: 8px;
display: flex;
overflow: hidden;
flex-shrink: 0;
}
.icon-container > :deep(svg:not(.icon-grid)) {
.icon-container:hover .size-buttons {
opacity: 1;
}
.icon-container > :deep(svg.preview-icon) {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
color: var(--vp-c-neutral);
opacity: 0.8;
transition: all 0.3s;
margin: auto;
}
.studio-button {
position: absolute;
bottom: 8px;
right: 8px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
color: var(--vp-c-text-2);
opacity: 0;
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
z-index: 2;
.icon-container > :deep(svg.preview-icon[data-size='1000%']) {
width: 100%;
height: 100%;
}
.icon-container:hover .studio-button,
.studio-button:focus-visible {
opacity: 1;
.icon-container > :deep(svg.preview-icon[data-size='200%']) {
width: 48px;
height: 48px;
}
.studio-button:hover,
.studio-button:focus-visible {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
.icon-container > :deep(svg.preview-icon[data-size='100%']) {
width: 24px;
height: 24px;
}
.icon-component.customizable {
will-change: width, height, stroke-width, stroke;
/* color: var(--customize-color, currentColor);
stroke-width: var(--customize-strokeWidth, 2); */
}
.size-buttons {
position: absolute;
bottom: 8px;
left: 50%;
padding: 4px;
border-radius: 12px;
transform: translateX(-50%);
display: flex;
z-index: 99;
background: var(--vp-code-tab-bg);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.size-buttons.show {
opacity: 1;
}
.size-button {
color: var(--vp-code-tab-text-color);
padding: 2px 8px;
}
.size-button.active {
background: var(--vp-c-neutral-inverse);
box-shadow: var(--vp-shadow-1);
border-radius: 4px;
color: var(--vp-code-tab-active-text-color);
text-decoration: none;
}
</style>
@@ -70,7 +70,6 @@ const prettyName = props.name
class="showcase"
v-if="iconComponent"
>
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">
<div class="placeholder"></div>
@@ -236,18 +235,17 @@ const prettyName = props.name
.showcase-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 48px;
margin-inline-start: 24px;
margin-block-start: 48px;
gap: 32px;
background: var(--vp-code-block-bg);
padding: 32px;
border-radius: 8px;
}
.showcase-item {
padding: 24px;
border-radius: 8px;
background-color: var(--vp-c-bg);
box-shadow:
var(--vp-shadow-4),
-24px -24px 0 var(--vp-c-bg-soft);
box-shadow: var(--vp-shadow-2);
display: flex;
align-items: center;
justify-content: center;
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
const MAX_VISIBLE_TAGS = 5;
const props = defineProps<{
tags?: string[];
}>();
const tags = computed(() => props.tags ?? []);
const expanded = ref(false);
const hiddenCount = computed(() => Math.max(tags.value.length - MAX_VISIBLE_TAGS, 0));
const showAll = computed(() => expanded.value || hiddenCount.value === 0);
// The overlay reuses this component when switching icons, so collapse again
watch(tags, () => {
expanded.value = false;
});
</script>
<template>
<ul
v-if="tags.length"
class="icon-tags"
>
<!-- v-show keeps hidden tags in the rendered HTML so crawlers can index them -->
<li
v-for="(tag, index) in tags"
v-show="showAll || index < MAX_VISIBLE_TAGS"
:key="tag"
class="tag"
>
{{ tag }}
</li>
<li
v-if="!showAll"
class="more"
>
<button
type="button"
class="more-button"
:aria-label="`Show ${hiddenCount} more tags`"
@click="expanded = true"
>
+{{ hiddenCount }}
</button>
</li>
</ul>
</template>
<style scoped>
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
display: flex;
flex-wrap: wrap;
align-items: center;
list-style-type: disc;
gap: 4px 8px;
/* Clip bullets hanging before tags that start a wrapped line, padding leaves room for the focus ring */
overflow: hidden;
padding: 4px;
margin: 4px -4px 12px;
}
.tag {
margin-top: 0;
padding-left: 0px;
padding-right: 12px;
list-style-position: outside;
white-space: nowrap;
}
.tag:first-child {
list-style: none;
margin-left: 0;
padding-left: 0;
}
.more {
margin-top: 0;
list-style: none;
}
.more-button {
font: inherit;
color: var(--vp-c-text-2);
font-size: 12px;
padding: 0 8px;
margin-left: -4px;
border-radius: 16px;
background-color: var(--vp-c-bg-alt);
transition:
color 0.25s,
background-color 0.25s;
}
.more-button:hover,
.more-button:focus-visible {
color: var(--vp-c-text-1);
background-color: var(--vp-c-default-soft);
}
.more-button:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 2px;
}
</style>
@@ -163,7 +163,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location);
const url = new URL(window.location.href);
url.pathname = '/icons/categories';
if (searchQueryDebounced.value) {
@@ -35,7 +35,6 @@ const SORTING = [
},
]
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
@@ -72,6 +71,8 @@ const sortedIcons = computed(() => {
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
case 'release-date':
return [...props.icons].sort((a, b) => {
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
return bDate - aDate;
@@ -172,7 +173,7 @@ watch(searchQueryDebounced, () => {
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location);
const url = new URL(window.location.href);
url.pathname = '/icons/';
if (searchQueryDebounced.value) {
@@ -207,6 +208,7 @@ function handleCloseDrawer() {
<template #start-icon>
<Icon
:iconNode="listSortDescending"
name="list-sort-descending"
class="chevron-icon"
aria-hidden="true"
/>
@@ -39,7 +39,7 @@ interface Placeholder {
finePrint?: string;
}
const brandPlaceholders: Placeholder[] = shallowReadonly([
const brandPlaceholders: readonly Placeholder[] = shallowReadonly([
{
title: 'Boooo! What a scary brand logo!',
message:
@@ -103,7 +103,7 @@ const brandPlaceholders: Placeholder[] = shallowReadonly([
},
]);
const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
const notFoundPlaceholders: readonly Omit<Placeholder, 'title'>[] = shallowReadonly([
{
message: 'We’ve looked for this icon for a bird’s eye view, but could not find it.',
icon: markRaw(bird),
@@ -130,7 +130,7 @@ const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
},
]);
function randomItem<T>(arr: T[]): T {
function randomItem<T>(arr: readonly T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
@@ -9,7 +9,6 @@ defineProps<{
<template>
<section class="related-icons">
<h2 class="title">More icons like this</h2>
<IconGrid :icons="icons" />
</section>
</template>
@@ -34,8 +34,8 @@ const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? document
});
syncRef(color, colorCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
syncRef(size, sizeCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr', transform: { ltr: String } });
syncRef(size, sizeCssVar, { direction: 'ltr', transform: { ltr: String } });
function resetStyle() {
color.value = STYLE_DEFAULTS.color;
@@ -61,6 +61,50 @@
}
}
.search-bar :deep(.select-button) {
padding: 11px 14px;
background-color: var(--vp-c-bg-alt);
color: var(--vp-c-text-2);
flex-shrink: 0;
flex: 1;
font-weight: 500;
cursor: pointer;
}
.search-bar :deep(.select-button):hover,
.search-bar :deep(.select-button):focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.search-bar :deep(.select-options) {
min-width: 160px;
}
.search-bar :deep(.select-option) {
padding-right: 36px;
padding-left: 8px;
}
.search-bar :deep(.select-icon) {
display: none;
}
.search-bar :deep(.select-button) .select-text {
display: none;
}
@media (min-width: 640px) {
.search-bar :deep(.select-button) {
padding: 11px 16px 11px 16px;
}
.search-bar :deep(.select-button) .select-text {
display: block;
}
}
@media (min-width: 960px) {
.search-bar {
padding-top: 32px;
@@ -68,4 +112,7 @@
top: calc(var(--vp-layout-top-height, 0px) + 64px);
}
}
</style>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
import { useData } from 'vitepress/dist/client/theme-default/composables/data.js'
import FakeInput from '../base/FakeInput.vue';
import { useRouter } from 'vitepress';
import { data } from '../home/HomeHeroIconsCard.data'
@@ -41,7 +41,7 @@ import {
sparkles,
} from '../../../data/iconNodes';
import createElement from 'lucide/src/createElement';
import { IconNode } from 'lucide';
import { IconNode } from 'lucide/src/types';
const icons: Component[] = [
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar.js';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import { computed } from 'vue';
import SurveyAskOverlay from '../SurveyAskOverlay.vue';
@@ -2,7 +2,7 @@ import packageDataList from '../../../data/packageData.json';
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
import fetchPackages from '../../../lib/fetchPackages';
export default {
const loader = {
async load() {
const packageJsonList = await fetchPackages();
@@ -29,3 +29,8 @@ export default {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -1,5 +1,5 @@
import { onMounted, onUpdated, onUnmounted } from 'vue';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils.js';
/*
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
@@ -1,10 +1,10 @@
import Fuse from 'fuse.js';
import Fuse, { type FuseOptionKeyObject } from 'fuse.js';
import { shallowRef, computed, Ref } from 'vue';
const useSearch = <T>(
query: Ref<string>,
collection: Ref<T[]>,
keys: Fuse.FuseOptionKeyObject<T>[] = [],
keys: FuseOptionKeyObject<T>[] = [],
) => {
const index = shallowRef(
new Fuse(collection.value, {
+8
View File
@@ -152,6 +152,10 @@ html {
.VPHomeHero .container {
flex-direction: row;
}
.VPDoc:has(.vp-doc[class*=' _icons_']) {
padding: 32px 32px 0;
}
}
.VPNavBarHamburger .container > span {
@@ -286,6 +290,10 @@ html {
--example-guidance-label-bg: var(--vp-c-danger-1);
}
.example-guidance--caution {
--example-guidance-label-bg: var(--vp-c-warning-1);
}
@media (min-width: 640px) {
.example-block {
display: grid;
+11 -2
View File
@@ -1,11 +1,17 @@
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
export interface Alias {
name: string;
deprecationReason: string;
deprecated: boolean;
}
export interface IconMetaData {
tags: string[];
categories: string[];
contributors: string[];
aliases?: string[];
aliases?: Alias[];
deprecated?: boolean;
deprecationReason?: string;
toBeRemovedInVersion?: string;
@@ -18,6 +24,8 @@ export interface IconEntity extends IconMetaData {
externalLibrary?: ExternalLibs;
createdRelease?: Release;
changedRelease?: Release;
fromFork?: boolean;
awaitingRelease?: boolean;
popularity?: number;
}
@@ -43,7 +51,8 @@ export interface PackageItem {
packageDirname?: string;
description: string;
icon: string;
iconDark: string;
iconDark?: string;
iconClass?: string;
shields: Shield[];
source: string;
documentation: string;
+2 -2
View File
@@ -1,5 +1,5 @@
const chunkArray = <ArrayType>(stream: ArrayType, size: number) => {
return stream.reduce<ArrayType[][]>(
const chunkArray = <ItemType>(stream: ItemType[], size: number) => {
return stream.reduce<ItemType[][]>(
(chunks, item, idx, arr) =>
idx % size == 0 ? [...chunks, arr.slice(idx, idx + size)] : chunks,
[],
@@ -0,0 +1,25 @@
import type { IconNode } from '../types';
function getInnerSvg(iconNode: IconNode) {
return iconNode
.map(([tag, attrs]) => {
const attributes = Object.entries(attrs)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return `<${tag} ${attributes}/>`;
})
.join('')
.replace(/>[\r\n ]+</g, '><')
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
.trim();
}
export default function getStudioLink(
name: string,
iconNode: IconNode,
utmMedium = 'icon-detail-preview',
) {
const base64InnerSvg = btoa(getInnerSvg(iconNode ?? []));
return `https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=${utmMedium}`;
}
+5
View File
@@ -1,3 +1,4 @@
/// <reference types="vitepress/client" />
import { type IconNode } from '@lucide/vue/src/types';
import Vue from 'vue';
@@ -31,7 +32,11 @@ declare module '*.node.json' {
}
declare global {
// Defined by VitePress at build time, used by its VPDocAsideCarbonAds component
const __CARBON__: boolean;
interface Window {
_carbonads?: unknown;
ExpoSnack?: {
/**
* Initialize all snack players on the page
+1 -1
View File
@@ -17,7 +17,7 @@ This guide explains how to design and prepare icons for Lucide.
## What this guide covers
<OverviewLinkGrid>
<OverviewLink v-for="item in resourcesSidebar[2].items.slice(1, -1)" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
<OverviewLink v-for="item in resourcesSidebar[3].items.slice(1, -1)" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
</OverviewLinkGrid>
## Use your preferred design tool
-237
View File
@@ -1,237 +0,0 @@
---
title: Accessibility
description: Learn how to make your icons accessible to all users, including those with disabilities.
---
# Accessibility in depth
Icons are pictures that show what something means without using words. They can be very helpful
because they can quickly give information.
However, not everyone can understand them easily. When using icons it is very important to consider
the following aspects of accessibility.
::: info
By default, we hide icons from screen readers using `aria-hidden="true"`.
You can make them accessible yourself by following the guidelines below.
:::
## Provide visible labels
Icons are a helpful tool to improve perception, but they aren't a replacement for text.
In most cases, it is probably a good idea to also provide a textual representation of your icon's
function.
<!--@include: ../../docs/images/a11y/visible-labels.svg -->
## Contrast
Ensure there's enough contrast between the icon and its background so that it's visible to people
with low vision or color vision deficiencies.
We recommend
following [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
<!--@include: ../../docs/images/a11y/contrast.svg -->
## Use of color
Avoid relying solely on color to convey meaning in icons, as some users may have color blindness.
Instead, use additional visual cues like shape, shading or text.
<!--@include: ../../docs/images/a11y/use-of-color.svg -->
## Interactivity
Ensure that interactive icons are accessible via keyboard navigation and provide clear feedback when
activated.
<!--@include: ../../docs/images/a11y/interactivity.svg -->
In most cases this is easily done by wrapping them in icon buttons.
## Minimum target size
Small targets can be difficult to click or touch, if your icon is interactive, we recommend that it
should have a minimum target size of 44×44 pixels.
<!--@include: ../../docs/images/a11y/target-size.svg -->
In practice, this doesn't necessarily mean that the icon itself should be this large, only its
interactive wrapper element.
## Meaningfulness
Icons should represent concepts or actions in a universally understandable way. Avoid using abstract
or ambiguous, or culture-specific symbols that might confuse some users.
<!--@include: ../../docs/images/a11y/meaningfulness.svg -->
## Consistency
Maintain consistency in icon design and usage across your interface to help users learn and
understand their meanings more easily.
<!--@include: ../../docs/images/a11y/consistency.svg -->
## Text Alternatives
You may have to provide text labels or alternative text descriptions for icons, especially for
screen readers used by people with visual impairments.
However: descriptions should only be provided to standalone icons that aren't purely decorative, as
providing accessible names to non-functional elements only increases clutter when using screen
readers.
### On standalone icons
Icons are usually very unlikely to stand on their own with no semantically meaningful wrapper
element. In most cases they will be part of a badge, button (including icon buttons), navigation
item or other interactive UI element.
::: warning
In case some of your icons stand alone, and they serve a non-decorative function, make sure to
provide the appropriate accessible label for them.
:::
<!--@include: ../../docs/images/a11y/alttext-standalone.svg -->
In general try to avoid using functional icons with no interactivity, we recommend that:
1) you either add a visible description next to them, or
2) place them in badges, labels or on buttons, and at least add a tooltip to them.
In any such case, it is preferred that the accessible name be provided for these interactive
elements (badges, buttons, nav items etc.) only, _not_ the icons themselves.
### On buttons
Do not provide an accessible label to icons when used on a button, as this label will be read out by
screen readers, leading to nonsensical text.
<!--@include: ../../docs/images/a11y/alttext-buttons.svg -->
::: details Code examples
```tsx
// Don't do this
<button>
<Plus aria-label="Plus icon"/>
Add document
</button>
// Do this, just leave it
<button>
<Plus/>
Add document
</button>
```
:::
### On icon buttons
Icon buttons are buttons that do not contain any visible text apart from the icon itself (think of
the close button of a dialog for example).
As previously stated, you should provide your accessible label on the icon button itself, not the
contained icon.
<!--@include: ../../docs/images/a11y/alttext-iconbuttons.svg -->
::: details Code examples
```tsx
// Don't do this
<button class="btn-icon">
<House/>
</button>
// Don't do this either
<button class="btn-icon">
<House aria-label="Home icon"/>
</button>
// This works but might not be the best solution, see below
<button aria-label="Go to home" class="btn-icon">
<House/>
</button>
// Do this instead
<button class="btn-icon">
<House/>
<span class="visually-hidden">Go to home</span>
</button>
```
:::
## A note on `aria-label`
Although you could provide accessible labels to your elements via the `aria-label` attribute, we
generally recommend avoiding this and instead suggest that you use your chosen CSS framework's "
visually hidden" utility whenever possible. You can
[read more about why `aria-label` might not be the best solution](https://gomakethings.com/revisting-aria-label-versus-a-visually-hidden-class/).
### Example - Radix UI
Use [Radix UI's built-in accessible icon utility component](https://www.radix-ui.com/primitives/docs/utilities/accessible-icon).
```tsx
import { ArrowRightIcon } from 'lucide-react';
import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
<AccessibleIcon label="Next item">
<ArrowRightIcon />
</AccessibleIcon>
```
### Example - Bootstrap
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="visually-hidden">Phone number</span>
</div>
```
### Example - Tailwind CSS
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="sr-only">Phone number</span>
</div>
```
If you're not sure, you may consider learning more
about [how to hide content.](https://www.a11yproject.com/posts/how-to-hide-content/)
## Further resources
We also recommend checking out the following resources about accessibility:
- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG21/)
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
- [Learn accessibility on web.dev](https://web.dev/learn/accessibility)
- [Inclusive Components](https://inclusive-components.design/)
- [A11yTalks](https://www.a11ytalks.com/)
- [A11y automation tracker](https://a11y-automation.dev/)
- [The A11Y Project](https://www.a11yproject.com/)
<style>
svg.a11y-example {
max-width: calc(100% + 48px);
margin: 0 -24px;
}
@media (min-width: 480px) {
svg.a11y-example {
margin: 0;
max-width: 100%;
}
}
</style>
+1 -1
View File
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Angular.
+1 -1
View File
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Astro.
+1 -1
View File
@@ -26,7 +26,7 @@ In addition to design, code is also important. Assets like icons can significant
Icons are pictures that show what something means without using words. They can be very helpful because they can quickly give information.
However, not everyone can understand them easily. Read more about [how to use Lucide in an accessible way](./accessibility.md).
However, not everyone can understand them easily. Read more about [how to use Lucide in an accessible way](../how-to/accessibility.md).
## Official Packages
+1 -1
View File
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in your app.
+1 -1
View File
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Preact.
+1 -1
View File
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in React.
+1 -1
View File
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Solid.
+1 -1
View File
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Svelte.
+1 -1
View File
@@ -63,7 +63,7 @@ Also, a `llms.txt` is now available for LLMs to use.
We have improved the accessibility of our icons by setting `aria-hidden` to `true` by default. This change ensures that screen readers will ignore icons that are purely decorative, improving the overall accessibility of your applications. If you need to make an icon accessible, you can provide an appropriate `aria-label` or add a `title` attribute to the icon element.
See our [accessibility in-depth guide](/guide/accessibility) for more details and best practices on making your icons accessible.
See our [accessibility in-depth guide](/how-to/accessibility) for more details and best practices on making your icons accessible.
## Removed UMD build, only ESM and CJS now
+1 -1
View File
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Vue.
+271
View File
@@ -0,0 +1,271 @@
---
title: Accessibility
description: Learn how to make your icons accessible to all users, including those with disabilities.
---
<script setup>
import {
menu,
pawPrint,
shirt,
umbrella,
userRoundCog,
} from '~/.vitepress/data/iconNodes';
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
</script>
# Accessibility in depth
Icons are pictures that convey meaning without using words.
They can help people scan an interface, recognize actions, and understand status quickly.
Not everyone understands icons easily. Use the following accessibility rules when you add icons to an interface.
::: tip
Lucide icons are hidden from screen readers by default with `aria-hidden="true"`.
Add accessible labels when icons carry meaning or act as controls.
:::
## Provide visible labels
Icons can improve scanning, but they aren't a replacement for text.
Text helps people understand actions and navigation more reliably than icons alone.
:::: example
::: do <span aria-hidden="true">On this page <LucideIcon name="menu" :iconNode="menu" /></span>
Provide a written description of your interactive elements.
:::
::: dont <LucideIcon name="menu" :iconNode="menu" />
Don't rely on icons alone to communicate what elements do.
:::
::::
## Provide enough contrast
Use enough contrast between an icon and its background. Low contrast makes icons harder to see for people with low vision or color vision deficiencies.
Follow [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) when contrast requirements apply.
:::: example
::: do ../images/a11y/contrast_do.svg?raw=true
Use a contrast ratio of at least 4.5:1.
:::
::: dont ../images/a11y/contrast_dont.svg?raw=true
Don't use low contrast.
:::
::::
## Do not rely on color alone
Use non-color cues like shape, shading, or text to mark state, status, and validity. Color can support meaning, but it must not be the only cue.
:::: example
::: do ../images/a11y/state_do.svg?raw=true
Mark state with non-color visual cues too.
:::
::: dont ../images/a11y/state_dont.svg?raw=true
Don't mark state only with color.
:::
::::
## Make interactive icons focusable
Wrap interactive icons in semantic elements. Icons that perform actions need keyboard access and clear feedback when activated.
:::: example
::: do ../images/a11y/interactive_icons_do.svg?raw=true
Wrap interactive icons in semantic elements, such as buttons, so they are focusable.
:::
::: dont ../images/a11y/interactive_icons_dont.svg?raw=true
Don't leave interactive icons unable to be focused.
:::
::::
## Use a large enough target size
Use a large enough target size for interactive icon controls. Small targets can be difficult to click or tap.
:::: example
::: do ../images/a11y/target_size_do.svg?raw=true
Keep the target size at a minimum of 44 by 44 pixels.
:::
::: dont ../images/a11y/target_size_dont.svg?raw=true
Don't make the target size smaller than 44 by 44 pixels.
:::
::::
The icon itself does not need to be 44 by 44 pixels. The interactive wrapper needs to meet the target size.
## Choose universal symbols when possible
Choose icons that are widely recognized for your use case. Avoid icons that only make sense to a narrow audience.
:::: example
::: do <span aria-hidden="true"><LucideIcon name="shirt" :iconNode="shirt" /> Clothing</span>
Pick icons that are universal symbols for your use case.
:::
::: dont <span aria-hidden="true"><LucideIcon name="umbrella" :iconNode="umbrella" /> Clothing</span>
Don't pick icons that are not universally recognizable for your use case.
:::
::::
## Avoid cultural or linguistic shortcuts
Choose icons with a clear connection to the concept. Puns, idioms, and culture-specific references can be difficult to understand or translate.
:::: example
::: do <span aria-hidden="true"><LucideIcon name="user-round-cog" :iconNode="userRoundCog" /> Role bearers</span>
Pick icons with a clear connection to the concept you want to represent.
:::
::: dont <span aria-hidden="true"><LucideIcon name="paw-print" :iconNode="pawPrint" /> Role bearers</span>
Don't pick icons based on puns or other cultural or linguistic cues.
:::
::::
## Use different icons for different meanings
Use different icons for different meanings. Reusing one icon for unrelated concepts makes the interface harder to learn.
:::: example
::: do ../images/a11y/distinct_use_case_do.svg?raw=true
Use different icons for distinct purposes or meanings.
:::
::: dont ../images/a11y/distinct_use_case_dont.svg?raw=true
Don't use the same icon for multiple distinct purposes or meanings, especially in the same context.
:::
::::
## Use the same icon for the same meaning
Use the same icon for the same meaning. Consistent icon use helps people recognize familiar actions across an interface.
:::: example
::: do ../images/a11y/same_use_case_do.svg?raw=true
Use the same icon for the same purpose or function.
:::
::: dont ../images/a11y/same_use_case_dont.svg?raw=true
Don't use different icons for the same purpose or function.
:::
::::
## Hide decorative icons from assistive technology
Keep decorative icons hidden from screen readers. Decorative icons repeat nearby text or add visual styling without adding meaning.
:::: example
::: do ../images/a11y/decorative_icons_do.svg?raw=true
Omit `aria-label` on decorative icons.
:::
::: dont ../images/a11y/decorative_icons_dont.svg?raw=true
Don't provide accessible labels to decorative icons.
:::
::::
## Label functional standalone icons carefully
Avoid standalone functional icons when possible. If an icon carries meaning without nearby text or an interactive wrapper, give that icon an accessible label.
:::: example
::: do ../images/a11y/functional_icons_do.svg?raw=true
Provide an accessible label for functional icons, but avoid standalone functional icons when possible.
:::
::: dont ../images/a11y/functional_icons_dont.svg?raw=true
Don't provide an accessible label for purely decorative icons.
:::
::::
In many cases, add visible text, place the icon inside a badge or button, or add a tooltip to the meaningful wrapper instead.
## Label icon buttons on the button
Give icon buttons an accessible name. Put the name on the button or in visually hidden text inside the button, not on the icon.
:::: example
::: do ../images/a11y/icon_button_do.svg?raw=true
Provide the accessible label for icon buttons themselves.
:::
::: dont ../images/a11y/icon_button_dont.svg?raw=true
Don't provide the accessible label for icons on icon buttons.
:::
::::
::: details Code examples
```tsx
// Don't do this
<button className="btn-icon">
<House />
</button>
// Don't do this either
<button className="btn-icon">
<House aria-label="Home icon" />
</button>
// This works, but visually hidden text is usually more reliable
<button aria-label="Go to home" className="btn-icon">
<House />
</button>
// Prefer this
<button className="btn-icon">
<House />
<span className="visually-hidden">Go to home</span>
</button>
```
:::
## A note on `aria-label`
You can provide accessible labels with `aria-label`, but visually hidden text is often more reliable.
Read [why `aria-label` might not be the best solution](https://gomakethings.com/revisting-aria-label-versus-a-visually-hidden-class/) for more context.
### Radix UI
Use [Radix UI's built-in accessible icon utility component](https://www.radix-ui.com/primitives/docs/utilities/accessible-icon).
```tsx
import { ArrowRightIcon } from 'lucide-react';
import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
<AccessibleIcon label="Next item">
<ArrowRightIcon />
</AccessibleIcon>;
```
### Bootstrap
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="visually-hidden">Phone number</span>
</div>
```
### Tailwind CSS
```html
<div>
<i data-lucide="phone" aria-hidden="true"></i>
<span class="sr-only">Phone number</span>
</div>
```
If you are not sure which hiding technique to use, read [how to hide content](https://www.a11yproject.com/posts/how-to-hide-content/).
## Further resources
Use these accessibility resources:
- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG21/)
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
- [Learn accessibility on web.dev](https://web.dev/learn/accessibility)
- [Inclusive Components](https://inclusive-components.design/)
- [A11yTalks](https://www.a11ytalks.com/)
- [A11y automation tracker](https://a11y-automation.dev/)
- [The A11Y Project](https://www.a11yproject.com/)
+190
View File
@@ -0,0 +1,190 @@
---
title: How to pick the right icon
description: Learn when to use icons, when to avoid them, and how to choose icons that make interfaces easier to understand.
---
<script setup>
import {
badgeCheck,
badgeX,
check,
download,
folderSearch,
globe,
house,
imageOff,
layoutList,
mail,
menu,
plus,
search,
settings,
smartphone,
sparkles,
trash,
x,
} from '~/.vitepress/data/iconNodes';
const customCheck = [['path', {d: 'M19.05 5.253c.26.1.42.352.63.571.159.166.547.536.672.69.088.11.158.28.257.397-.075.202-.212.133-.453.038q-.315.238-.625.485l-.208-.159c-.14-.016-.045.182-.058.253-.135.685-1.145 1.03-1.61 1.376-.101.074-.32.424-.474.382-.04-.012-.133-.101-.154-.09q-.151.298-.293.598c-.446.156-.638.591-.906.928-.728.913-1.366 1.874-2.074 2.787-.138.178-.225.463-.403.606-.074.059-.265-.029-.208.158l-.14-.092c-.005.125.06.254.004.357a2.2 2.2 0 0 1-.259.37c-.034.031-.111-.041-.135-.006-.04.062-.007.272-.064.356-.052.076-.237.135-.262.203-.091.247-.092.572-.282.774-.052.054-.217.06-.228.081-.018.043.084.127.081.162-.014.178-.278.295-.367.42-.29.41-.527.885-.818 1.293-.076.107-.166.248-.264.392l-.613 1.273-1.16-1.208c-.135-.19-.27-.393-.326-.558-.02-.061.103-.138.07-.178-.035-.044-.13-.008-.152-.024-.158-.11-.489-.358-.524-.553-.006-.037.064-.128.052-.144-.007-.007-.08.026-.107.007-.232-.164-.33-.343-.49-.56-.312-.415-.828-.734-1.167-1.143-.074-.09.013-.309 0-.318-.031-.02-.147.077-.225.058-.26-.062-.407-.385-.632-.459-.112-.037-.343.032-.452.008-.087-.02-.208-.189-.285-.139-.045.031-.141.164-.173.168-.03.002-.007-.097-.028-.117-.088-.08-.091-.16-.165-.245-.112-.133-.569-.21-.304-.827l.008-.019c.053-.117.15-.228.215-.358.066-.13.444-.992.535-1.009.132-.017.256.314.309.331.037.012.12-.042.172-.03.165.034.205.13.449.005q-.019.203-.044.403c.093-.009.1.188.142.204.034.014.105-.055.14-.038.038.02.034.143.106.173.117.05.282-.14.364-.121.085.02.103.205.237.195l.081-.042.093-.167c.059.01-.002.126.034.157.043.036.14-.042.17-.018.024.02-.004.122.03.143.051.03.131-.088.172-.088.02 0 .246.089.257.097.08.061-.04.214.009.279.134.177.404.211.592.307a.44.44 0 0 1 .187.201c-.443.26-.494.747-.035.114-.006.148.19.048.233.14.026.059-.021.165.04.224.11.104.448.016.077.427.192.062.147.31.55.03.054.014-.083.296-.079.36.007.062.15.423.182.473.034.05.095.134.16.146.081.013.498-.275.116.19.12.07.138.118.162.255.003.025-.063.082-.055.092.017.02.095.014.13.046.06.052-.057.202.158.02l.102.016c-.002-.055-.01-.114-.017-.165-.004-.02-.062-.059-.07-.087-.017-.07.128-.127-.067-.248l.239-.002q-.025-.079-.048-.158l-.181-.116c.244.073.228-.075.308-.09.017-.001.1.05.141.036.049-.017.066-.096.117-.119l-.245-.226c-.003-.103.256.03.303-.001a1 1 0 0 0 .081-.167c0-.088-.217-.207-.058-.354.029-.025.117-.005.14-.032.038-.048-.023-.306-.164-.417.226.063.258-.149.345-.163.026-.003.146.072.202.045.045-.022.013-.179.06-.214.05-.036.159.012.181-.004.02-.015-.046-.089-.027-.117.062-.086.244-.078.294-.144.07-.097-.105-.346-.152-.502.03-.01.095.003.11-.018.193-.262.448-.534.69-.766.112-.107.648-.183.066-.485-.06-.11.38-.267.428-.323.047-.057-.01-.173.024-.22.101-.142.244-.19.295-.42.013-.06-.058-.193-.037-.218.028-.029.157.02.196-.015.09-.084.2-.288.292-.364.017-.014.112.054.16.053.185-.001.26-.11-.042-.33.261.017.204-.216.288-.332.177-.246.607-.597.673-.896.04-.19-.083-.303.191-.382.061-.018.131-.027.204-.048.174-.05.491.067.31-.278.19-.053.24-.199.553-.058l-.127-.196.04-.048.167.144c.12-.089.053-.437.148-.523.16-.145.649-.427.864-.481.114-.03.018-.153.273.051l.01-.097q-.05-.096-.102-.19c.103-.09.297-.11.398-.194.18-.152.34-.528.605-.572.067-.012.215.057.26.02.065-.054-.063-.258.02-.326.142-.118.59-.1.715-.218.038-.035.005-.123.029-.151.099-.122.203-.187.24-.383m-9.145 12.15c-.111.13.148.094.324-.011zm-1.57-2.539c.012-.033-.085-.048-.137-.005-.199.168.085.152.138.005m3.356-1.475q.068.022.133.045l.018-.033q-.093-.06-.186-.124z', fill: 'currentColor', stroke: 'none'}]];
const customMail = [['path', {fill:"currentColor", stroke: 'none', 'fill-rule': "evenodd", d: "M22 20H2V7.003l10 6.546 10-6.546z M22 5.905 12 11.45 2 5.905V4h20z"}]];
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
</script>
# How to pick the right icon
Icons can make an interface easier to scan. They work best when people already understand the symbol or when the icon appears with a clear text label.
Use text instead of an icon when the meaning is complex, abstract, or easy to misunderstand.
## Match the interface style
Use icons with the same visual language in the same interface. Mixed styles can make related controls look unrelated.
:::: example
::: do <span class="g-4"><LucideIcon name="house" :iconNode="house" size="48" /> <LucideIcon name="plus" :iconNode="plus" size="48" /> <LucideIcon name="check" :iconNode="check" size="48" /> <LucideIcon name="mail" :iconNode="mail" size="48" /></span>
Use one icon style for related controls.
:::
::: dont <span class="g-4"><LucideIcon name="house" :iconNode="house" strokeWidth="1" size="48" /> <LucideIcon name="plus" :iconNode="plus" strokeWidth="3" size="48" /> <LucideIcon name="check" :iconNode="customCheck" size="48" /> <LucideIcon name="mail" :iconNode="customMail" size="48" /></span>
Don't mix unrelated icon styles in the same interface.
:::
::::
## Use familiar icons for familiar actions
Icons work well for common actions and destinations that people already recognize from other interfaces.
:::: example
::: do <span class="g-4"><LucideIcon name="search" :iconNode="search" size="48" /> <LucideIcon name="house" :iconNode="house" size="48" /> <LucideIcon name="settings" :iconNode="settings" size="48" /> <LucideIcon name="x" :iconNode="x" size="48" /></span>
Use icons for common actions with widely understood symbols.
:::
::: dont <span>"strategy", "optimize", "synergy", "innovation"</span>
Don't use icons alone for abstract concepts with no common symbol.
:::
::::
## Use icons as visual anchors
Icons can help people scan lists, menus, cards, and repeated controls. The icon needs to support the text, not compete with it.
:::: example
::: do ../images/how-to/visual_anchor_do.svg?raw=true
Use icons to make associated options easier to scan.
:::
::: dont ../images/how-to/visual_anchor_dont.svg?raw=true
Don't add icons to every line when they only create visual clutter.
:::
::::
## Use icons in compact interfaces
Icons can help in tight spaces, such as mobile navigation, toolbars, tables, and compact controls. Keep labels when the icon is not obvious.
:::: example
::: do ../images/how-to/compact_interfaces_labels_do.svg?raw=true
Keep labels in all layout variants when space allows.
:::
::: caution ../images/how-to/compact_interfaces_labels_dont.svg?raw=true
Use icon-only controls only when a visible label does not fit.
In these cases, make sure that the label is accessible via hover, focus, or tap.
:::
::::
:::: example
::: do ../images/how-to/compact_interfaces_do.svg?raw=true
Keep text labels next to icons in compact layouts, even when space is sparse.
:::
::: dont ../images/how-to/compact_interfaces_dont.svg?raw=true
Don't remove labels in compact layouts when the meaning without them is unclear.
:::
::::
:::: example
::: caution ../images/how-to/compact_interfaces_caution_1.svg?raw=true
Use icon-only controls only when the icon represents a widely understood action.
:::
::: caution ../images/how-to/compact_interfaces_caution_2.svg?raw=true
Use icon-only controls only when the label is available on hover, focus, or tap.
:::
::::
## Choose the clearest symbol
Use a familiar symbol when one exists. Novel symbols make people stop and interpret the interface.
:::: example
::: do <span><LucideIcon name="trash" :iconNode="trash" /> `trash`<br>for deleting an image</span>
Use a familiar symbol for a familiar action.
:::
::: dont <span><LucideIcon name="image-off" :iconNode="imageOff" /> `image-off`<br>for deleting an image</span>
Don't make people learn a new symbol for a common action.
:::
::::
## Match the context, not every detail
Choose the icon that makes the action clearest in context. A more specific icon is better only when the extra detail helps people understand the action.
:::: example
::: do ../images/how-to/appropriate_context_do.svg?raw=true
Use clear action icons when the surrounding interface already provides the object context.
:::
::: dont ../images/how-to/appropriate_context_dont.svg?raw=true
Don't add object details when the context already makes the object clear.
:::
::::
## Keep icon meaning consistent
Use the same icon for the same meaning, and use different icons for different meanings.
:::: example
::: do ../images/how-to/consistent_icon_meaning_do.svg?raw=true
Use different icons for distinct purposes or meanings.
:::
::: dont ../images/how-to/consistent_icon_meaning_dont.svg?raw=true
Don't use the same icon for multiple distinct purposes or meanings.
:::
::::
:::: example
::: do ../images/how-to/consistent_icon_function_do.svg?raw=true
Use the same icon for the same purpose or function.
:::
::: dont ../images/how-to/consistent_icon_function_dont.svg?raw=true
Don't use different icons for the same purpose or function.
:::
::::
## Do not iconize repeated status decoration
Avoid adding an icon to every repeated status marker when color, weight, text, or layout already communicates the state. Repeated status icons can turn useful list structure into visual noise.
:::: example
::: do ../images/how-to/repeated_status_do.svg?raw=true
Use the lightest cue that makes the status clear.
:::
::: dont ../images/how-to/repeated_status_dont.svg?raw=true
Don't add repeated status icons when they only restate what the list already shows.
:::
::::
## Do not replace built-in control cues
Do not add icons to controls that already show people how they work. Native controls are easier to recognize when they behave and look familiar.
:::: example
::: do ../images/how-to/native_ui_elements_do.svg?raw=true
Use the built-in visual cue when the control already communicates its behavior.
:::
::: dont ../images/how-to/native_ui_elements_dont.svg?raw=true
Don't replace native controls with similarly themed interactive icons.
:::
::::
<style>
.g-4 {
display: flex;
flex-direction: row;
gap: 1rem;
}
</style>

Some files were not shown because too many files have changed in this diff Show More