diff --git a/packages/lucide-icons/.npmignore b/packages/lucide-icons/.npmignore
new file mode 100644
index 000000000..fc28543eb
--- /dev/null
+++ b/packages/lucide-icons/.npmignore
@@ -0,0 +1,10 @@
+stats
+node_modules
+tests
+scripts
+build
+src
+babel.config.js
+jest.config.js
+rollup.config.js
+yarn.error.log
diff --git a/packages/lucide-icons/README.md b/packages/lucide-icons/README.md
new file mode 100644
index 000000000..d034cd2c1
--- /dev/null
+++ b/packages/lucide-icons/README.md
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+Lucide icon library for web applications.
+
+
+
+
+ [](https://www.npmjs.com/package/lucide)
+ 
+ [](https://lucide.dev/license)
+
+
+
+ About
+ ·
+ Icons
+ ·
+ Documentation
+ ·
+ License
+
+
+# Lucide
+
+Implementation of the lucide icon library for web applications.
+
+## Installation
+
+```sh
+pnpm add lucide
+```
+
+```sh
+npm install lucide
+```
+
+```sh
+yarn add lucide
+```
+
+```sh
+bun add lucide
+```
+
+### CDN
+
+```html
+
+
+
+
+
+```
+
+## Documentation
+
+For full documentation, visit [lucide.dev](https://lucide.dev/guide/packages/lucide)
+
+## Community
+
+Join the [Discord server](https://discord.gg/EH6nSts) to chat with the maintainers and other users.
+
+## License
+
+Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/license).
+
+## Sponsors
+
+
+
+
+
+
+
+### Awesome backers 🍺
+
+
+
diff --git a/packages/lucide-icons/package.json b/packages/lucide-icons/package.json
new file mode 100644
index 000000000..e13cf5b4e
--- /dev/null
+++ b/packages/lucide-icons/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@lucide/icons",
+ "description": "A Lucide icon library that contains icon data in a standard Lucide format.",
+ "version": "0.0.1",
+ "license": "ISC",
+ "homepage": "https://lucide.dev",
+ "bugs": "https://github.com/lucide-icons/lucide/issues",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/lucide-icons/lucide.git",
+ "directory": "packages/lucide"
+ },
+ "keywords": [
+ "Lucide",
+ "HTML",
+ "Feather",
+ "Icons",
+ "Icon",
+ "SVG",
+ "Feather Icons",
+ "Fontawesome",
+ "Font Awesome"
+ ],
+ "amdName": "lucide-icons",
+ "source": "src/lucide-icons.js",
+ "main": "dist/cjs/lucide-icons.js",
+ "main:umd": "dist/umd/lucide-icons.js",
+ "module": "dist/esm/lucide-icons.js",
+ "unpkg": "dist/umd/lucide-icons.min.js",
+ "typings": "dist/lucide-icons.d.ts",
+ "sideEffects": false,
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundle",
+ "copy:license": "cp ../../LICENSE ./LICENSE",
+ "clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts",
+ "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --withDynamicImports --separateAliasesFile --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts",
+ "build:bundle": "rollup -c rollup.config.mjs",
+ "test": "pnpm build:icons && vitest run",
+ "test:watch": "vitest watch",
+ "version": "pnpm version --git-tag-version=false"
+ },
+ "devDependencies": {
+ "@lucide/build-icons": "workspace:*",
+ "@lucide/rollup-plugins": "workspace:*",
+ "@rollup/plugin-replace": "^6.0.1",
+ "@testing-library/jest-dom": "^6.1.6",
+ "jest-serializer-html": "^7.1.0",
+ "rollup": "^4.22.4",
+ "rollup-plugin-dts": "^6.1.0",
+ "typescript": "^4.9.3",
+ "vite": "5.4.14",
+ "vitest": "^1.1.1"
+ }
+}
diff --git a/packages/lucide-icons/rollup.config.mjs b/packages/lucide-icons/rollup.config.mjs
new file mode 100644
index 000000000..a4840c024
--- /dev/null
+++ b/packages/lucide-icons/rollup.config.mjs
@@ -0,0 +1,83 @@
+import plugins from '@lucide/rollup-plugins';
+import replace from '@rollup/plugin-replace';
+import dts from 'rollup-plugin-dts';
+import pkg from './package.json' with { type: 'json' };
+
+const outputFileName = pkg.name;
+const outputDir = 'dist';
+const inputs = ['src/lucide.ts'];
+const bundles = [
+ {
+ format: 'umd',
+ inputs,
+ outputDir,
+ minify: true,
+ },
+ {
+ format: 'umd',
+ inputs,
+ outputDir,
+ },
+ {
+ format: 'cjs',
+ inputs,
+ outputDir,
+ },
+ {
+ format: 'esm',
+ inputs,
+ outputDir,
+ preserveModules: true,
+ },
+];
+
+const configs = bundles
+ .map(({ inputs, outputDir, format, minify, preserveModules }) =>
+ inputs.map((input) => ({
+ input,
+ plugins: [
+ // This is for lucide plugin to replace an argument in createIcons so it is easier to use with UMD.
+ ...(format === 'umd'
+ ? [
+ replace({
+ 'icons = {}': 'icons = iconAndAliases',
+ delimiters: ['', ''],
+ preventAssignment: false,
+ }),
+ ]
+ : []),
+ ...plugins({ pkg, minify }),
+ ],
+ output: {
+ name: outputFileName,
+ ...(preserveModules
+ ? {
+ dir: `${outputDir}/${format}`,
+ }
+ : {
+ file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
+ }),
+ format,
+ sourcemap: true,
+ preserveModules,
+ },
+ })),
+ )
+ .flat();
+
+const typesFileConfig = {
+ input: inputs[0],
+ output: [
+ {
+ file: `${outputDir}/${outputFileName}.d.ts`,
+ format: 'esm',
+ },
+ ],
+ plugins: [
+ dts({
+ include: ['src'],
+ }),
+ ],
+};
+
+export default [...configs, typesFileConfig];
diff --git a/packages/lucide-icons/scripts/exportTemplate.mjs b/packages/lucide-icons/scripts/exportTemplate.mjs
new file mode 100644
index 000000000..63cc3bb0d
--- /dev/null
+++ b/packages/lucide-icons/scripts/exportTemplate.mjs
@@ -0,0 +1,36 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import base64SVG from '@lucide/build-icons/utils/base64SVG.mjs';
+
+export default async ({
+ componentName,
+ iconName,
+ children,
+ getSvg,
+ deprecated,
+ deprecationReason,
+}) => {
+ const svgContents = await getSvg();
+ const svgBase64 = base64SVG(svgContents);
+
+ return `
+import type { LucideIconData } from '../types';
+
+/**
+ * @name ${iconName}
+ * @description Lucide SVG icon node.
+ *
+ * @preview  - https://lucide.dev/icons/${iconName}
+ * @see https://lucide.dev/guide/packages/lucide - Documentation
+ *
+ * @returns {Array}
+ * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
+ */
+const ${componentName}: LucideIconData = ${JSON.stringify({
+ name: iconName,
+ size: 24,
+ node: children,
+ })}
+
+export default ${componentName};
+`;
+};
diff --git a/packages/lucide-icons/src/aliases/index.ts b/packages/lucide-icons/src/aliases/index.ts
new file mode 100644
index 000000000..bbe66d0a2
--- /dev/null
+++ b/packages/lucide-icons/src/aliases/index.ts
@@ -0,0 +1,3 @@
+export * from './aliases';
+export * from './prefixed';
+export * from './suffixed';
diff --git a/packages/lucide-icons/src/defaultAttributes.ts b/packages/lucide-icons/src/defaultAttributes.ts
new file mode 100644
index 000000000..98ba5dc04
--- /dev/null
+++ b/packages/lucide-icons/src/defaultAttributes.ts
@@ -0,0 +1,12 @@
+import { SVGProps } from './types';
+
+const defaultAttributes: SVGProps = {
+ xmlns: 'http://www.w3.org/2000/svg',
+ fill: 'none',
+ stroke: 'currentColor',
+ 'stroke-width': 2,
+ 'stroke-linecap': 'round',
+ 'stroke-linejoin': 'round',
+};
+
+export default defaultAttributes;
diff --git a/packages/lucide-icons/src/icons/.gitkeep b/packages/lucide-icons/src/icons/.gitkeep
new file mode 100644
index 000000000..e3d85f774
--- /dev/null
+++ b/packages/lucide-icons/src/icons/.gitkeep
@@ -0,0 +1 @@
+Folder for generated icons
diff --git a/packages/lucide-icons/src/iconsAndAliases.ts b/packages/lucide-icons/src/iconsAndAliases.ts
new file mode 100644
index 000000000..098bba469
--- /dev/null
+++ b/packages/lucide-icons/src/iconsAndAliases.ts
@@ -0,0 +1,2 @@
+export * from './icons';
+export * from './aliases';
diff --git a/packages/lucide-icons/src/lucide-icons.prefixed.ts b/packages/lucide-icons/src/lucide-icons.prefixed.ts
new file mode 100644
index 000000000..c916995f3
--- /dev/null
+++ b/packages/lucide-icons/src/lucide-icons.prefixed.ts
@@ -0,0 +1,3 @@
+export * as icons from './icons';
+export * from './aliases/prefixed';
+export * from './types';
diff --git a/packages/lucide-icons/src/lucide-icons.suffixed.ts b/packages/lucide-icons/src/lucide-icons.suffixed.ts
new file mode 100644
index 000000000..551638884
--- /dev/null
+++ b/packages/lucide-icons/src/lucide-icons.suffixed.ts
@@ -0,0 +1,3 @@
+export * as icons from './icons';
+export * from './aliases/suffixed';
+export * from './types';
diff --git a/packages/lucide-icons/src/lucide-icons.ts b/packages/lucide-icons/src/lucide-icons.ts
new file mode 100644
index 000000000..947ffb76e
--- /dev/null
+++ b/packages/lucide-icons/src/lucide-icons.ts
@@ -0,0 +1,4 @@
+export * from './icons';
+export * as icons from './icons';
+export * from './aliases';
+export * from './types';
diff --git a/packages/lucide-icons/src/types.ts b/packages/lucide-icons/src/types.ts
new file mode 100644
index 000000000..b4edfb1e3
--- /dev/null
+++ b/packages/lucide-icons/src/types.ts
@@ -0,0 +1,10 @@
+export type SVGProps = Record;
+
+export type LucideIconNode = [tag: string, attrs: SVGProps][];
+
+export type LucideIconData = {
+ name: string;
+ node: LucideIconNode;
+} & ({ size: number } | { width: number; height: number });
+
+export type Icons = { [key: string]: LucideIconData };
diff --git a/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap
new file mode 100644
index 000000000..4b1d355d0
--- /dev/null
+++ b/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap
@@ -0,0 +1,19 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`createElement > should match the snapshot 1`] = `
+
+`;
diff --git a/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap
new file mode 100644
index 000000000..16fd120aa
--- /dev/null
+++ b/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap
@@ -0,0 +1,76 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`createIcons > should add custom attributes 1`] = `
+
+`;
+
+exports[`createIcons > should read elements from DOM and replace icon with alias name 1`] = `
+
+`;
+
+exports[`createIcons > should read elements from DOM and replace it with icons 1`] = `
+
+`;
diff --git a/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap
new file mode 100644
index 000000000..7a975d7b3
--- /dev/null
+++ b/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap
@@ -0,0 +1,3 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`combineClassNames > should returns a string of classNames 1`] = `"item item1 item2 item3 item4 item5 item6 item7 item8 item9"`;
diff --git a/packages/lucide-icons/tests/createElement.spec.ts b/packages/lucide-icons/tests/createElement.spec.ts
new file mode 100644
index 000000000..f4a896fd3
--- /dev/null
+++ b/packages/lucide-icons/tests/createElement.spec.ts
@@ -0,0 +1,37 @@
+import { describe, expect, it } from 'vitest';
+import { createElement, House } from '../src/lucide-icons';
+import { getOriginalSvg } from './helpers';
+
+describe('createElement', () => {
+ it('should create SVG Element', () => {
+ const HomeSVG = createElement(House);
+
+ expect(HomeSVG.tagName).toBe('svg');
+ });
+
+ it('should match the snapshot', () => {
+ const HomeSVG = createElement(House);
+
+ expect(HomeSVG.outerHTML).toMatchSnapshot();
+ });
+
+ it('should create SVG Element with attributes', () => {
+ const HomeSVG = createElement(House, { fill: 'red' });
+
+ expect(HomeSVG.getAttribute('fill')).toBe('red');
+ });
+
+ it('should create SVG Element with class name', () => {
+ const HomeSVG = createElement(House, { class: 'icon' });
+
+ expect(HomeSVG.getAttribute('class')).toBe('icon');
+ });
+
+ it('should create the correct svg element', () => {
+ const HomeSVG = createElement(House);
+
+ const svg = getOriginalSvg('house', undefined, false);
+
+ expect(HomeSVG.outerHTML).toBe(svg);
+ });
+});
diff --git a/packages/lucide-icons/tests/helpers.ts b/packages/lucide-icons/tests/helpers.ts
new file mode 100644
index 000000000..1e8e90fcd
--- /dev/null
+++ b/packages/lucide-icons/tests/helpers.ts
@@ -0,0 +1,17 @@
+import fs from 'fs';
+import path from 'path';
+import { parseSync, stringify } from 'svgson';
+
+const ICONS_DIR = path.resolve(__dirname, '../../../icons');
+
+export const getOriginalSvg = (iconName: string, aliasName?: string, setAttrs = true) => {
+ const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8');
+ const svgParsed = parseSync(svgContent);
+
+ if (setAttrs) {
+ svgParsed.attributes['data-lucide'] = aliasName ?? iconName;
+ svgParsed.attributes['class'] = `lucide lucide-${aliasName ?? iconName}`;
+ }
+
+ return stringify(svgParsed, { selfClose: false });
+};
diff --git a/packages/lucide-icons/tests/lucide.spec.ts b/packages/lucide-icons/tests/lucide.spec.ts
new file mode 100644
index 000000000..4cc231400
--- /dev/null
+++ b/packages/lucide-icons/tests/lucide.spec.ts
@@ -0,0 +1,91 @@
+import { describe, expect, it } from 'vitest';
+import { createIcons, icons } from '../src/lucide-icons';
+import { getOriginalSvg } from './helpers';
+
+describe('createIcons', () => {
+ it('should read elements from DOM and replace it with icons', () => {
+ document.body.innerHTML = ``;
+
+ createIcons({ icons });
+
+ const svg = getOriginalSvg('volume-2');
+
+ expect(document.body.innerHTML).toBe(svg);
+ expect(document.body.innerHTML).toMatchSnapshot();
+ });
+
+ it('should customize the name attribute', () => {
+ document.body.innerHTML = ``;
+
+ createIcons({
+ icons,
+ nameAttr: 'data-custom-name',
+ });
+
+ const hasSvg = !!document.querySelector('svg');
+
+ expect(hasSvg).toBeTruthy();
+ });
+
+ it('should add custom attributes', () => {
+ document.body.innerHTML = ``;
+
+ const attrs = {
+ class: 'lucide lucide-volume-2 icon custom-class',
+ fill: 'black',
+ };
+
+ createIcons({ icons, attrs });
+
+ const element = document.querySelector('svg') as SVGSVGElement;
+ const attributes = element.getAttributeNames();
+
+ const attributesAndValues = attributes.reduce(
+ (acc, item) => {
+ acc[item] = element.getAttribute(item);
+
+ return acc;
+ },
+ {} as Record,
+ );
+
+ expect(document.body.innerHTML).toMatchSnapshot();
+
+ expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
+ });
+
+ it('should inherit elements attributes', () => {
+ document.body.innerHTML = ``;
+
+ const attrs = {
+ 'data-theme-switcher': 'light',
+ };
+
+ createIcons({ icons });
+
+ const element = document.querySelector('svg') as SVGSVGElement;
+ const attributes = element.getAttributeNames();
+
+ const attributesAndValues = attributes.reduce(
+ (acc, item) => {
+ acc[item] = element.getAttribute(item);
+
+ return acc;
+ },
+ {} as Record,
+ );
+
+ expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
+ });
+
+ it('should read elements from DOM and replace icon with alias name', () => {
+ document.body.innerHTML = ``;
+
+ createIcons({ icons });
+
+ const svg = getOriginalSvg('grid-3x3', 'grid');
+
+ expect(document.body.innerHTML).toBe(svg);
+ expect(document.body.innerHTML).toMatchSnapshot();
+ });
+});
diff --git a/packages/lucide-icons/tests/replaceElement.spec.ts b/packages/lucide-icons/tests/replaceElement.spec.ts
new file mode 100644
index 000000000..16d2742ab
--- /dev/null
+++ b/packages/lucide-icons/tests/replaceElement.spec.ts
@@ -0,0 +1,64 @@
+import { describe, it, expect } from 'vitest';
+import { getAttrs, getClassNames, combineClassNames } from '../src/replaceElement';
+
+describe('getAtts', () => {
+ it('should returns attrbrutes of an element', () => {
+ const element = {
+ attributes: [
+ {
+ name: 'class',
+ value: 'item1 item2 item4',
+ },
+ {
+ name: 'date-name',
+ value: 'volume',
+ },
+ ],
+ };
+
+ const attrs = getAttrs(element as unknown as Element);
+
+ expect(attrs.class).toBe(element.attributes[0].value);
+ });
+});
+
+describe('getClassNames', () => {
+ it('should returns an array when giving class property of string', () => {
+ const elementAttrs = {
+ class: 'item1 item2 item3',
+ };
+
+ const attrs = getClassNames(elementAttrs);
+ expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3']));
+ });
+
+ it('should returns an array when givind class property with an array', () => {
+ const elementAttrs = {
+ class: ['item1', 'item2', 'item3'],
+ };
+
+ const attrs = getClassNames(elementAttrs);
+ expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3']));
+ });
+});
+
+describe('combineClassNames', () => {
+ it('should returns a string of classNames', () => {
+ const arrayOfClassnames: (string | Record)[] = [
+ 'item',
+ {
+ class: ['item1', 'item2', 'item3'],
+ },
+ {
+ class: ['item4', 'item5', 'item6'],
+ },
+ {
+ class: ['item7', 'item8', 'item9'],
+ },
+ ];
+
+ const combinedClassNames = combineClassNames(arrayOfClassnames);
+
+ expect(combinedClassNames).toMatchSnapshot();
+ });
+});
diff --git a/packages/lucide-icons/tests/setupVitest.js b/packages/lucide-icons/tests/setupVitest.js
new file mode 100644
index 000000000..03a0244af
--- /dev/null
+++ b/packages/lucide-icons/tests/setupVitest.js
@@ -0,0 +1,5 @@
+import { expect } from 'vitest';
+import '@testing-library/jest-dom/vitest';
+import htmlSerializer from 'jest-serializer-html';
+
+expect.addSnapshotSerializer(htmlSerializer);
diff --git a/packages/lucide-icons/tsconfig.json b/packages/lucide-icons/tsconfig.json
new file mode 100644
index 000000000..7fdaedbf6
--- /dev/null
+++ b/packages/lucide-icons/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "declaration": true,
+ "noEmitOnError": true,
+ "noFallthroughCasesInSwitch": true,
+ "moduleResolution": "node",
+ "module": "ESNext",
+ "target": "ESNext",
+ "esModuleInterop": true,
+ "allowJs": true,
+ "lib": ["esnext", "dom"],
+ "resolveJsonModule": true,
+ "sourceMap": true,
+ "outDir": "./dist",
+ },
+}
diff --git a/packages/lucide-icons/vitest.config.mts b/packages/lucide-icons/vitest.config.mts
new file mode 100644
index 000000000..295f35b09
--- /dev/null
+++ b/packages/lucide-icons/vitest.config.mts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './tests/setupVitest.js',
+ }
+});