An icon set: Lucide, delivered as inline SVG through a template tag #8

Closed
opened 2026-09-05 12:08:42 +00:00 by tiagoagueda · 0 comments
Owner

Observation

what is the most widespread, open source, rich, set of icons to use on any project. i need to incorporate a set of icons so i can switch the theme dark/light on the top right corner of the dashboard

This issue is the icon set and how it reaches a template. The switch itself is the next issue, which depends on this one.

What exists today

Postulo has no icons. The whole template tree holds exactly one <svg>, and it is a bar chart (applications/insights.html). Navigation, buttons and status are all words — deliberate at 0.1.0, but a header switch needs a sun and a moon, and #4's channels, #7's avatar and the board will all want a glyph or two. Better to choose once than to paste SVGs one at a time.

The candidates

Checked at the registries on 2026-09-05, not from memory:

Set Licence Icons in the current release GitHub stars Notes
Font Awesome Free 7.3 CC BY 4.0 icons, OFL fonts, MIT code 2,883 (2,001 solid, 273 regular, 609 brands) 76.9k The most widespread by a distance. Freemium: Free is the subset, Pro is paid, and the catalogue steers to Pro at every turn. The icon licence requires attribution.
Lucide 1.41 ISC 2,066 24.4k Fork of Feather. One style: 24px grid, 2px stroke, currentColor. The default set of shadcn/ui and of most current Tailwind projects; pushed the day before this was written.
Heroicons 2.2 MIT 324, in four variants 23.8k By Tailwind Labs. Beautiful and small; last push May 2026.
Tabler 3.46 MIT 6,184 (5,130 outline, 1,054 filled) 21.6k The richest. Same 24px stroke grid as Lucide, so the two mix without anyone noticing.
Material Symbols / MDI Apache 2.0 ~2,500 / ~7,000 11.3k (MDI) Google's look: a different grid and weight from the rest of this interface.

Proposal: Lucide

"Most widespread" has two honest answers. Font Awesome is the most installed, but it is a freemium product, and a project that will never paywall a feature should not draw its interface from a set whose full catalogue sits behind one. Lucide is the most adopted of the fully open sets in current web projects, and the most actively maintained.

  • One consistent style that matches what is already here: the stroke look of Tailwind's own Heroicons, beside Inter and the ink palette. stroke="currentColor" means every icon takes the surrounding text colour, so the existing dark: variants colour icons for free.
  • Rich enough at 2,066, and when it lacks a glyph, Tabler is the fallback, because it shares the grid. Rule: one set; an exception gets a comment saying why.
  • The theme icons exist: sun, moon, sun-moon, monitor, laptop.
  • Licence: ISC, as permissive as MIT and compatible with AGPL-3.0; no attribution clause. Each SVG from lucide-static already carries <!-- @license lucide-static v1.41.0 - ISC --> in its first line, which is all the credit the licence asks for. A line in the README's credits does not hurt.

How an icon reaches a template

Three ways, in order of preference.

  1. Inline SVG through a template tag, from committed files. {% icon "sun" class="size-4" %} reads static/icons/sun.svg, cached at first use. Only the icons Postulo uses are committed, copied from the lucide-static npm package by a sync:icons script beside build:css — the same arrangement as the compiled stylesheet: Node is needed to change it, never to run it. Inline SVG is untouched by the CSP, inherits currentColor, is swapped by htmx like any markup, and is aria-hidden="true" by default with aria-label when the tag is given a label, so an icon-only button is still announced. A dozen files of 400 bytes, no runtime dependency, no new Python dependency; the tag is thirty lines.
  2. A PyPI package that does the same with the whole catalogue in the wheel: heroicons (Adam Johnson; Django 6.0 in its classifiers), django-lucide (1.3.1, February 2026; Python 3.14 in its classifiers) or lucide (1.1.4, July 2026; classifiers stop at Django 5.0). Convenient, but a runtime dependency on a small third-party package to ship ten SVGs, on its author's release cadence. Not worth it while the tag is thirty lines.
  3. @iconify/tailwind4 (1.2.3, MIT) with @iconify-json/lucide: classes like icon-[lucide--sun] generated at CSS build time, the SVG embedded as a mask-image data URI. The production CSP allows it — img-src 'self' data: in prod.py — and it needs no template code, and it opens every Iconify set. But a mask is single-colour, cannot carry an accessible name without extra markup, and grows the committed stylesheet. Overkill for a handful of icons.

Proposal: the first.

Rules that come with it

  • Icons decorate; they never replace the word. A button is text with an icon beside it. The one exception is the header, where space is short, and there the button carries a visually hidden label and a title.
  • Sizes: size-4 inline with text, size-5 in buttons, size-6 or more in empty states; the stroke width stays at Lucide's default 2.
  • Keep the set's name out of templates: {% icon "sun" %}, not {% lucide "sun" %}, so a change of set is one directory swap and no template edit.

Where it lands first

The theme switch. Then, cheaply and only where a glyph carries meaning: the alerts (info, success, warning, error), the status pills on the board, empty states, the navigation.

Classification

Enhancement. Not breaking: no schema, no setting, nothing an operator has to do.

Open questions

  1. Commit only the icons used (proposal), or the whole lucide-static sprite — one file of about a megabyte, any icon available without a build step, and a megabyte on every first visit?
  2. Should the sync script record the lucide-static version somewhere a bump shows as a diff? The SVG header already does (@license lucide-static v1.41.0), so a bump touches every file; that is probably enough.
## Observation > what is the most widespread, open source, rich, set of icons to use on any project. i need to incorporate a set of icons so i can switch the theme dark/light on the top right corner of the dashboard This issue is the icon set and how it reaches a template. The switch itself is the next issue, which depends on this one. ## What exists today Postulo has no icons. The whole template tree holds exactly one `<svg>`, and it is a bar chart (`applications/insights.html`). Navigation, buttons and status are all words — deliberate at 0.1.0, but a header switch needs a sun and a moon, and #4's channels, #7's avatar and the board will all want a glyph or two. Better to choose once than to paste SVGs one at a time. ## The candidates Checked at the registries on 2026-09-05, not from memory: | Set | Licence | Icons in the current release | GitHub stars | Notes | |---|---|---|---|---| | Font Awesome Free 7.3 | CC BY 4.0 icons, OFL fonts, MIT code | 2,883 (2,001 solid, 273 regular, 609 brands) | 76.9k | The most widespread by a distance. Freemium: Free is the subset, Pro is paid, and the catalogue steers to Pro at every turn. The icon licence requires attribution. | | **Lucide 1.41** | ISC | 2,066 | 24.4k | Fork of Feather. One style: 24px grid, 2px stroke, `currentColor`. The default set of shadcn/ui and of most current Tailwind projects; pushed the day before this was written. | | Heroicons 2.2 | MIT | 324, in four variants | 23.8k | By Tailwind Labs. Beautiful and small; last push May 2026. | | Tabler 3.46 | MIT | 6,184 (5,130 outline, 1,054 filled) | 21.6k | The richest. Same 24px stroke grid as Lucide, so the two mix without anyone noticing. | | Material Symbols / MDI | Apache 2.0 | ~2,500 / ~7,000 | 11.3k (MDI) | Google's look: a different grid and weight from the rest of this interface. | ## Proposal: Lucide "Most widespread" has two honest answers. Font Awesome is the most *installed*, but it is a freemium product, and a project that will never paywall a feature should not draw its interface from a set whose full catalogue sits behind one. Lucide is the most *adopted* of the fully open sets in current web projects, and the most actively maintained. - **One consistent style** that matches what is already here: the stroke look of Tailwind's own Heroicons, beside Inter and the ink palette. `stroke="currentColor"` means every icon takes the surrounding text colour, so the existing `dark:` variants colour icons for free. - **Rich enough** at 2,066, and when it lacks a glyph, Tabler is the fallback, because it shares the grid. Rule: one set; an exception gets a comment saying why. - **The theme icons exist:** `sun`, `moon`, `sun-moon`, `monitor`, `laptop`. - **Licence:** ISC, as permissive as MIT and compatible with AGPL-3.0; no attribution clause. Each SVG from `lucide-static` already carries `<!-- @license lucide-static v1.41.0 - ISC -->` in its first line, which is all the credit the licence asks for. A line in the README's credits does not hurt. ## How an icon reaches a template Three ways, in order of preference. 1. **Inline SVG through a template tag, from committed files.** `{% icon "sun" class="size-4" %}` reads `static/icons/sun.svg`, cached at first use. Only the icons Postulo uses are committed, copied from the `lucide-static` npm package by a `sync:icons` script beside `build:css` — the same arrangement as the compiled stylesheet: Node is needed to change it, never to run it. Inline SVG is untouched by the CSP, inherits `currentColor`, is swapped by htmx like any markup, and is `aria-hidden="true"` by default with `aria-label` when the tag is given a label, so an icon-only button is still announced. A dozen files of 400 bytes, no runtime dependency, no new Python dependency; the tag is thirty lines. 2. **A PyPI package that does the same with the whole catalogue in the wheel:** `heroicons` (Adam Johnson; Django 6.0 in its classifiers), `django-lucide` (1.3.1, February 2026; Python 3.14 in its classifiers) or `lucide` (1.1.4, July 2026; classifiers stop at Django 5.0). Convenient, but a runtime dependency on a small third-party package to ship ten SVGs, on its author's release cadence. Not worth it while the tag is thirty lines. 3. **`@iconify/tailwind4`** (1.2.3, MIT) with `@iconify-json/lucide`: classes like `icon-[lucide--sun]` generated at CSS build time, the SVG embedded as a `mask-image` data URI. The production CSP allows it — `img-src 'self' data:` in `prod.py` — and it needs no template code, and it opens every Iconify set. But a mask is single-colour, cannot carry an accessible name without extra markup, and grows the committed stylesheet. Overkill for a handful of icons. Proposal: the first. ## Rules that come with it - **Icons decorate; they never replace the word.** A button is text with an icon beside it. The one exception is the header, where space is short, and there the button carries a visually hidden label and a `title`. - **Sizes:** `size-4` inline with text, `size-5` in buttons, `size-6` or more in empty states; the stroke width stays at Lucide's default 2. - **Keep the set's name out of templates:** `{% icon "sun" %}`, not `{% lucide "sun" %}`, so a change of set is one directory swap and no template edit. ## Where it lands first The theme switch. Then, cheaply and only where a glyph carries meaning: the alerts (info, success, warning, error), the status pills on the board, empty states, the navigation. ## Classification Enhancement. Not breaking: no schema, no setting, nothing an operator has to do. ## Open questions 1. Commit only the icons used (proposal), or the whole `lucide-static` sprite — one file of about a megabyte, any icon available without a build step, and a megabyte on every first visit? 2. Should the sync script record the `lucide-static` version somewhere a bump shows as a diff? The SVG header already does (`@license lucide-static v1.41.0`), so a bump touches every file; that is probably enough.
tiagoagueda added this to the 0.2.0 milestone 2026-09-05 12:08:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
Postulo/postulo#8
No description provided.