There’s no first-class
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!
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
Add the lucide
package.
pnpm install lucide
In 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
.
To render a single Lucide icon:
lucide
iconattributes
and children
array items from that icon<svg>
component
Spread attributes
directly on the element
Apply childElements
with the set:html
directive
---
// 1. Import any `lucide` icon
import { Accessibility } from "lucide";
// 2. Destructure the `attributes` and `children` array items
const [, attributes, children] = Accessibility;
// 3. Reduce children nodes to HTML string
const 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}
/>
LucideIcon
componentNow 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 icon
as a prop
Take rest ...props
so HTML and SVG attributes can be applied at the call site
Merge icon attributes
and component props
Apply munged attributes-props to exported svg
element
---
// 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}
/>
LucideIcon
componentTo 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} />
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" />
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}
/>
lucide
does not expose its IconNode
type for external use.
So, if you want to use
IconNode
for that icon in the LucideIcon
type declaration.svg
attributes you’d like to support.height
, width
, fill
, stroke-width
, etc.)src/components/lucide-icon.astro
---
// 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;
};
---
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. 😄