Add Lucide Icons to Astro

There’s no first-class Lucide integration for Astro. But making a custom one isn’t too hard.
This is the component I made to do the job:
---const { icon, ...props } = Astro.props;
const [, attributes, children] = icon;
const componentChildren = children ?.map( ([shape, attributes]) => `<${shape} ${Object.entries(attributes) .map(([k, v]) => `${k}="${v}"`) .join(" ")} />` ) .join("");
const componentAttributes = {...attributes, ...props}---
<svg {...componentAttributes} set:html={componentChildren}/>If this component looks reasonable to you, copy it and go forth.
If you’d like to learn how it works, let’s get it!
Our goal
For this build, our goal is to render icons directly into an Astro site — converting Lucide’s JSON files to inline <svg>s at build time.
This is just one of many ways to add Lucide icons to a site — the one that fits my needs best.
Initial setup
Add the lucide package.
pnpm install lucideIn the standard lucide package, icons are exported as JSON arrays with the following contents:
[0]: "svg"
[1]: element attributes
[2]: children
;[ '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', }, [ ['circle', [Object]], ['path', [Object]], ['path', [Object]], ['path', [Object]], ['path', [Object]], ],]We need to import these JSON files and render their data as HTML.
Render a single icon
To render a single Lucide icon:
- Import any
lucideicon - Destructure
attributesandchildrenarray items from that icon - Reduce children nodes to HTML string
- Render an
<svg>component- Spread
attributesdirectly on the element - Apply
childElementswith theset:htmldirective
- Spread
---// 1. Import any `lucide` iconimport { Accessibility } from "lucide";
// 2. Destructure the `attributes` and `children` array itemsconst [, attributes, children] = Accessibility;
// 3. Reduce children nodes to HTML stringconst componentChildren = children ?.map( ([child, attributes]) => `<${child} ${Object.entries(attributes) .map(([k, v]) => `${k}="${v}"`) .join(" ")} />` ) .join("\n");---
<!-- 4. render svg element --><svg {/* 4.1. Spread `attributes` directly on the element */} {...attributes} {/* 4.2. Apply `childElements` with the `set:html` directive */} set:html={childrenElements}/>Extract LucideIcon component
Now that we can render icons, let’s extract this code as a reusable component.
- Move the relevant code to
src/components/lucide-icon.astro - Refactor to
iconas a prop - Take rest
...propsso HTML and SVG attributes can be applied at the call site - Merge icon
attributesand componentprops - Apply munged attributes-props to exported
svgelement
---// 1. Take `icon` as a prop// 2. Take rest `props` import { Accessibility } from "lucide"; const { icon, ...props } = Astro.props;
const componentChildren = children ?.map( ([child, attributes]) => `<${child} ${Object.entries(attributes) .map(([k, v]) => `${k}="${v}"`) .join(" ")} />` ) .join("\n");
// 3. Merge `attributes` and `props` const componentAttributes = {...attributes, ...props}---
<svg {/* 4. Apply munged `componentAttributes` to svg */} {...attributes} {...componentAttributes} set:html={childrenElements}/>Use the LucideIcon component
To use our new LucideIcon component, import it along with any lucide icon.
Provide the icon JSON to LucideIcon using the icon prop.
---import LucideIcon from "#components/lucide-icon.astro";import { Accessibility } from "lucide";---
<LucideIcon icon={Accessibility} />Apply attributes to LucideIcon
The LucideIcon receives props that it merges with the default lucide values.
Use these to change SVG attributes like height, width, fill, and stroke-width.
Or apply common attributes like class and id.
---import LucideIcon from "#components/lucide-icon.astro";import { Accessibility } from "lucide";---
<LucideIcon icon={Accessibility} width="56" height="56" stroke-width="4" />Taking it further
My preference is to keep icon importing and SVG rendering separated. But you may find this cumbersome.
If so, create a facade for LucideIcon that exposes your favorite icons via string interface.
This could look something like this:
---import { Github as github, Youtube as youtube, Twitter as twitter, Instagram as instagram} from "lucide";
const icons = { github, youtube, twitter, instagram}
const { name = "github", ...props } = Astro.props;
if !(icons[name]) { return null }
const [, attributes, children] = icons[name];
const componentChildren = children ?.map( ([child, attributes]) => `<${child} ${Object.entries(attributes) .map(([k, v]) => `${k}="${v}"`) .join(" ")} />` ) .join("\n");
const componentAttributes = {...attributes, ...props}---
<svg {...componentAttributes} set:html={childrenElements}/>Go further with TypeScript
lucide does not expose its IconNode type for external use.
So, if you want to use my code above in TypeScript, you’ll need to get clever.
- Import just the type of any component.
- Infer the
IconNodefor that icon in theLucideIcontype declaration. - Add any optional
svgattributes you’d like to support.
(e.g.,height,width,fill,stroke-width, etc.)
---// 1. Import just the type of any component.import type { Accessibility } from "lucide";
type Props = { // 2. Infer the `IconNode` for that icon in the `LucideIcon` type declaration icon: typeof Accessibility;};---That’s it!
I hope that you found this useful in building an Astro site. If you’d like to see more Astro tips and tricks, bug me on Twitter/X or Discord. 😄