Introducing Hanami Icons

Most of what can be read here is around and for Ruby on Rails. My main apps still are powered by it. But monopolies are never good. Hanami is the one I get most excited about.

It is why I extracted the core features (pulling SVGs from remote repository, transforming filenames, etc.) of Rails Icons into a Ruby gem, called Icons, so other frameworks can have all the icons in their apps as well.

Hanami Icons is the next wrapper of the Icons gem for… drumroll… Hanami! It gives you the same interface as Rails Icons too, whether that is Heroicons, Lucide, Tabler or even a folder of your own SVGs.

<%== icon "check", class: "text-gray-500" %>

<%== icon "apple", library: "simple_icons", class: "text-black" %>

if you have seen Rails icons, you know Hanami Icons.

One interface, all the libraries

The work is done in Icons. If you do not know it: it syncs SVGs from their source repositories, normalizes names, handles variants and builds sprite markup. Hanami Icons just adds the Hanami-specific parts: a provider, view helpers and CLI generators.

Hanami Icons supports twelve first-party libraries: from good-old Feather to Heroicons to Lucide and Phosphor. That ranges from 49 icons in SidekickIcons to more than 9,000 in Phosphor. You can also add your own library (more on that below).

Install and generate

Hanami loads CLI extensions from the :cli Bundler group, while the view helpers need the gem at runtime. Add it to both:

gem "hanami_icons", group: [:default, :cli]

Then run:

bundle install
hanami generate icons --library=lucide

The generator writes config/providers/icons.rb, registers the icons provider, adds the helpers to app/views/helpers.rb and syncs the library. You can pick several libraries or only the variants you need:

hanami generate icons --libraries=heroicons lucide
hanami generate icons --library=heroicons --variants solid outline

The Hanami configuration can be used to set a default library and variant there:

HanamiIcons.configure do |config|
  config.default_library = "heroicons"
  config.default_variant = "outline"
end

Icons and sprites

The main helper takes the icon name and any attributes you want on the SVG:

icon "check"
icon "check", variant: "solid"
icon "check", library: "heroicons"
icon "check", class: "icon icon-small"
icon "check", aria: { label: "Save" }
icon "check", stroke_width: 2

A page with many icons repeats the same paths over and over. Sprites fix that. Generate one:

hanami generate icons:sprite

Then reference symbols instead of inlining every path:

<%== sprite_icon "check" %>
<%== sprite_icon "search", class: "icon icon-small fill-blue-500" %>

Point config.default_sprite_location at the generated file or at a CDN. Set it to nil to inline the sprite in your layout with icons_sprite. Check the README for more details on sprites. They give you the best of customisability and rendering optimisation.

Custom libraries and syncing

Any directory under app/assets/svg/icons is a “library”. Add a SVG and use it:

app/assets/svg/icons/simple_icons/apple.svg
icon "apple", library: "simple_icons"

Libraries with variants use a variant directory, like heroicons/outline/check.svg. If you want a starting point, hanami generate icons --library=simple_icons creates the folder and adds config.custom_library :simple_icons to the provider.

You can also sync a library from a git repository:

HanamiIcons.configure do |config|
  config.custom_library :my_icons, source: {
    url: "https://github.com/user/icons.git",
    variants: { default: "." }
  }
end

The icons:sync command runs before the app boots, so it does not load providers. It only sees the first-party libraries. Sync a custom library inside the app context instead:

bundle exec hanami run 'Icons::Sync.new(:my_icons).now'

For first-party libraries, syncing is a single command:

hanami generate icons:sync
hanami generate icons:sync --library=heroicons
hanami generate icons:sync --libraries=heroicons lucide

Run it whenever an upstream library adds icons you want. No bundle update hanami_icons needed.


Hanami Icons is at 0.6.0. It is still early. The generators and helpers have tests, but I want to hear from people using it in real apps before calling it 1.0. If something feels awkward, open an issue or send a pull request.

If Hanami Icons saves you an afternoon, a star helps other Hanami developers find it. ⭐

Product-minded Rails notes

Once a month: straightforward notes on improving UX in Rails—what to simplify, what to measure, and UI/frontend changes that move real usage.

Over to you…

What did you like about this article? Learned something knew? Found something is missing or even broken? 🫣 Let me (and others) know!

Comments are powered by Chirp Form

Want to read me more?