The project is in a healthy, maintained state
glimmer-dsl-web has no built-in icon story. glimmer-dsl-web-icons fills that gap: it ships icon data from popular icon libraries (Lucide, Heroicons, and Phosphor today) in glimmer-dsl-web's own native format -- arrays of [tag, attrs] pairs rendered as real SVG child elements -- plus a small Icon component and registry so a project can render `icon('circle-check', library: :lucide)` without hand-maintaining its own SVG paths.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.14
~> 13.0
 Project Readme

glimmer-dsl-web-icons

Icon library support for glimmer-dsl-web projects. Ships Lucide, Heroicons, and Phosphor today.

Why

glimmer-dsl-web has no built-in icon story: every project doing real UI work ends up hand-copying SVG path data into its own components. This gem fills that gap by shipping icon data from popular icon libraries in glimmer-dsl-web's own native format -- so an icon becomes a normal DSL call instead of a pile of hand-maintained SVG markup.

Status

Early. Three libraries ship today, deliberately chosen (after Lucide) to stress-test the design against real differences rather than guessed ones:

  • :lucide -- one style, stroke-based.
  • :heroicons_outline, :heroicons_solid, :heroicons_mini, :heroicons_micro -- Heroicons' own four style/size variants (24px stroke, 24px fill, 20px fill, 16px fill). Each is a real, separately designed icon set, not a resize.
  • :phosphor_thin, :phosphor_light, :phosphor_regular, :phosphor_bold, :phosphor_fill, :phosphor_duotone -- Phosphor's six "weights". Unlike Heroicons, all six share one identical root <svg> (fill-based, same viewBox) -- still verified per weight rather than assumed, since nothing guarantees a future release keeps it that way, but it turned out not to be where Phosphor's real difference is.

Each of these was added for a specific reason, not just to pad the list:

  • Heroicons showed the root <svg> attributes (fill/stroke/viewBox) aren't universal -- an earlier version hardcoded Lucide's stroke-based convention directly into the Icon component, and Heroicons' solid styles (fill: currentColor, no stroke at all) broke that assumption. Fixed by moving those attrs into the registry, per library -- register(library, icons, attrs: {...}) -- see registry.rb.
  • Phosphor was chosen specifically to test multi-color icon support, flagged from the start as an unvalidated gap (single-color libraries don't exercise it). Its duotone weight turned out to need no design change at all: the two-tone effect is just two <path>s sharing one fill: currentColor, where the background layer carries its own opacity="0.2" -- a per-shape attribute, which this gem's [[tag, attrs], ...] format already carried through generically from day one (the same mechanism that already handled Heroicons' per-path stroke-linecap). Confirmed against all 1512 duotone icons, not assumed from one example -- see generate/phosphor.rb.

The public API (library: option, one data file per library that self-registers with the registry, attrs: for whatever a library's icons share at the root) has now absorbed two real design pressures without breaking callers of the first library added under it.

Roadmap: planned libraries

Next additions are meant to match the icon-library coverage of rails_icons / Rails-Designer/icons -- the prior art this gem's two-layer shape (data registry + thin rendering component) is deliberately modeled on. rails_icons supports 12 libraries total; the full list, with the 3 already shipped checked off:

Not committed to this exact order, and not committed to shipping all nine remaining -- each one only earns a slot once it's actually confirmed against its real source (the way Lucide/Heroicons/Phosphor were), and only kept if it either works with the current design as-is or teaches it something new, the same bar Heroicons and Phosphor were held to above. Tabler and Feather are stroke-based like Lucide and are expected to need no design changes; Flags and Weather are the more likely candidates to surface something new (Flags in particular may not be simple monochrome shapes).

Installing in a Rails app

This assumes your app already has glimmer-dsl-web's Opal build pipeline set up (i.e. you followed glimmer-dsl-web's own Rails setup guide and have a working config/initializers/opal.rb).

  1. Add to your Gemfile and bundle install:

    gem 'glimmer-dsl-web-icons'
  2. Add it to config.opal.use_gems in config/initializers/opal.rb. This step is easy to miss and the gem will silently fail to compile into your browser bundle without it:

    config.opal.use_gems = %w[glimmer-dsl-web glimmer-dsl-web-icons]

    Why: Opal compiles your app/assets/opal code to JavaScript ahead of time, so there's no Ruby process in the browser to resolve a require later the way a normal Rails Gemfile gem would be. use_gems is the explicit whitelist that tells the Opal build step which gems' Ruby source to scan and compile into that bundle. Being in your Gemfile only makes a gem available to server-side Ruby -- any gem your Opal-side code requires has to also be listed here, and glimmer-dsl-web's own setup guide only shows this line with itself in the array, so it's worth calling out explicitly rather than leaving people to hit the failure and guess.

  3. In whichever Opal file needs icons, require this gem plus the library data file(s) you want (each library is opt-in, so nothing loads that you don't ask for):

    require 'glimmer-dsl-web-icons'
    require 'glimmer/dsl/web/icons/lucide'
    require 'glimmer/dsl/web/icons/heroicons' # if you want Heroicons too
    require 'glimmer/dsl/web/icons/phosphor'  # if you want Phosphor too

    Note: each data file above compiles its entire icon set into your Opal bundle once required -- Lucide alone is ~1800 icons, Phosphor's six weights ~9000 between them. There's no way yet to require only the handful of icons you actually use (that would mean generating one file per icon instead of one per library) -- a real tradeoff, tracked as a possible future change rather than solved here.

Usage

Every icon, from any library, is rendered by the same icon call -- there's no per-library Ruby API, only different library: values (and different icon names within each):

icon(name, library: :lucide, size: 24)
Option Required Default Meaning
name yes -- Icon name string, e.g. 'circle-check'. Can be passed positionally (icon('circle-check', ...), mirroring shadcn_glimmer's ShButton/ShIcon convention) or as name: -- both are equivalent.
library no :lucide Which registered library/variant to look the name up in (see per-library tables below). Its data file must be required first (Installing, step 3), or Icons.lookup raises UnknownLibraryError.
size no 24 Sets both width and height on the root <svg>, in pixels. The only option that works identically across every library.
attrs no {} Extra attributes merged onto the root <svg>, applied last (so they can override anything above). For attributes the call site knows about, not the library -- data-icon="inline-start", a class, an aria-label on a meaningful icon -- not for changing a library's own fill/stroke defaults.

An unrecognized icon name raises Icons::UnknownIconError rather than rendering nothing, so a typo fails loudly instead of silently.

Root <svg> attributes (fill/stroke/stroke-width/viewBox) come from whichever library: value you pick -- attrs: is for anything on top of that, not a way to override a library's whole convention. For example, a button component that needs data-icon for its own spacing CSS:

icon('git-branch', library: :lucide, attrs: {'data-icon' => 'inline-start'})

Lucide -- :lucide (default)

1,799 icons, one style, stroke-based.

icon('circle-check', library: :lucide, size: 20)
icon('circle-check', size: 20) # library: :lucide is the default, can be omitted

Root <svg> attrs: viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round".

Heroicons -- :heroicons_outline, :heroicons_solid, :heroicons_mini, :heroicons_micro

4 independently-designed style/size variants. The variant is the library: value -- there's no separate style:/variant: option. 324 icons per variant (316 for micro -- Heroicons ships a few fewer icons at that size).

icon('heart', library: :heroicons_outline) # 24px, stroke-based
icon('heart', library: :heroicons_solid)   # 24px, fill-based
icon('heart', library: :heroicons_mini)    # 20px, fill-based
icon('heart', library: :heroicons_micro)   # 16px, fill-based
library: viewBox fill stroke
:heroicons_outline 0 0 24 24 none currentColor, stroke-width: 1.5
:heroicons_solid 0 0 24 24 currentColor none
:heroicons_mini 0 0 20 20 currentColor none
:heroicons_micro 0 0 16 16 currentColor none

Phosphor -- :phosphor_thin, :phosphor_light, :phosphor_regular, :phosphor_bold, :phosphor_fill, :phosphor_duotone

6 weights, selected via library: the same way as Heroicons' variants. 1,512 icons per weight.

icon('heart', library: :phosphor_thin)
icon('heart', library: :phosphor_light)
icon('heart', library: :phosphor_regular)
icon('heart', library: :phosphor_bold)
icon('heart', library: :phosphor_fill)     # flat solid
icon('heart', library: :phosphor_duotone)  # two-tone: light bg shape + solid fg shape

All six weights share identical root <svg> attrs: viewBox="0 0 256 256" fill="currentColor". The thin/light/regular/bold difference is baked into each icon's own path data (there's no adjustable stroke-width the way Lucide has one), and duotone's two-tone look comes from two overlapping shapes at different opacities within the icon data itself -- nothing you pass at the call site changes it.

Development

No Bundler required to hack on this gem -- it's pure Ruby with only rake/minitest as dev dependencies (both ship with a standard Ruby install). Run the test suite with:

rake test

generate/lucide.rb, generate/heroicons.rb, and generate/phosphor.rb are dev-only scripts (not shipped in the gem) that regenerate their respective lib/glimmer/dsl/web/icons/*.rb files from a local npm install lucide / npm install heroicons / npm install @phosphor-icons/core checkout under references/ -- see each script's header comment for how to re-sync when a library releases new icons.

Adding another library

Each library is one file under lib/glimmer/dsl/web/icons/ that builds an {"icon-name" => [[tag, attrs], ...]} Hash and calls Glimmer::DSL::Web::Icons.register(:your_library, that_hash, attrs: {...}), where attrs: is whatever root <svg> attributes every icon in that library shares (see registry.rb). Whether a new library needs one registration or several (the way Heroicons and Phosphor both needed several) depends on whether it actually ships multiple independently-designed styles -- confirm that against the library's real source rather than assuming either way, the same way each of these three was. See "Roadmap" above for the specific libraries planned next.

License

MIT.