DEV Community

Cover image for Add Lucide Icons to Astro
chantastic
chantastic

Posted on • Originally published at chan.dev

4 1

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}
/>
Enter fullscreen mode Exit fullscreen mode

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 lucide
Enter fullscreen mode Exit fullscreen mode

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]],
  ],
];
Enter fullscreen mode Exit fullscreen mode

We need to import these JSON files and render their data as HTML.

Render a single icon

To render a single Lucide icon:

  1. Import any lucide icon
  2. Destructure attributes and children array items from that icon
  3. Reduce children nodes to HTML string
  4. Render an <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}
/>
Enter fullscreen mode Exit fullscreen mode

Extract LucideIcon component

Now that we can render icons, let's extract this code as a reusable component.

  1. Move the relevant code to src/components/lucide-icon.astro
  2. Refactor to icon as a prop
  3. Take rest ...props so HTML and SVG attributes can be applied at the call site
  4. Merge icon attributes and component props
  5. 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}
/>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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:

  1. Import just the type of any component.
  2. Infer the IconNode for that icon in the LucideIcon type declaration.
  3. Add any optional svg attributes 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;
};
---
Enter fullscreen mode Exit fullscreen mode

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. 😄

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (2)

Collapse
 
diomed profile image
May Kittens Devour Your Soul

those are very nice icons

Collapse
 
chantastic profile image
chantastic

they are indeed!

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay