Mike Macpherson

TL;DR: All 1600+ Lucide icons available as SVG strings in Python. pip install python-lucide, import, embed. No JavaScript, no icon fonts, no client-side rendering step. Every icon on this page is a real Lucide SVG, sourced from the package itself.

the problem

I like building HTML on the server. Not templates-with-holes, but actual programmatic markup — hiccup in Clojure, cottonmouth in Python, data structures that are the page. FastHTML takes that ethos and makes it a full framework: pure Python components, web-standards-first, no transpilation, no virtual DOM, no magic.

The pattern extends to interactivity. datastar.js, HTMX, and FastHTML’s own HTMX integration all follow the same principle: the server renders HTML, the browser just shows it. No client-side JavaScript framework sitting between you and your markup.

Lucide is a beautiful, well-maintained icon set — 1600+ icons, all SVG, consistent style. But its standard integration assumes a JavaScript runtime. You include the JS bundle and call lucide.createIcons(), which walks the DOM and replaces placeholder elements with SVG. That works fine for a traditional SPA. In a server-rendered world, though, you have to call lucide.createIcons() again after every DOM swap — every HTMX response, every Datastar fragment, every partial update. It’s a small thing, but it grates.

If you’re already building your markup on the server, SVG is just… more markup to include. That’s what python-lucide enables — it packages the Lucide icon set into an easy-to-use Python library so that every icon is just a function call away, no client-side initialization needed.

the approach

python-lucide packs all 1600+ Lucide SVGs into a ~780 KB SQLite database that ships with the package. One function call gets you an icon:

from lucide import lucide_icon

svg = lucide_icon("house")
# → '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" ...'

The return value is a plain string. Embed it in your HTML however you like — NotStr() in FastHTML, mark_safe() in Django, Markup() in Jinja2, or just concatenate it into your response body. It’s just SVG.

Customization is the natural Python API — keyword arguments, not config objects:

lucide_icon("heart", stroke="red", fill="pink", width=32, height=32)
lucide_icon("settings", cls="icon spin", stroke_width=1.5)

Here’s what those look like, rendered:

It works with anything that renders HTML on the server. FastHTML, Flask, Django, FastAPI, plain string concatenation — if you can return an HTML string, you can use these icons. No framework coupling, no special integration needed.

If 780 KB of icons is too much for your deployment, the included lucide-db CLI trims the database to just the icons you actually use:

lucide-db -i house,settings,heart,search -o slim.db
export LUCIDE_DB_PATH=slim.db