ExIcon
Generic icon library for Phoenix LiveView.
- Downloads icon sets and generates Phoenix LiveView function components.
- Generates components from a folder of your own SVG files.
- Extensible via behaviour to support multiple icon libraries.
- Icon library versions are set via configuration. Update your icons without updating this library.
- Generate components for all icons or only the ones you need.
- Customize the component attributes, or accept the global HTML attributes.
- Dev-only dependency. The library is only used for generating icon modules.
Installation
Add ex_icon to your list of dependencies in mix.exs:
def deps do
[
{:ex_icon, "~> 0.5.0", only: :dev}
]
end
Usage
ExIcon expects a configuration file named .ex_icon.exs in your project root.
[
icon_sets: [
lucide: [
# A module implementing the `ExIcon.Provider` behaviour.
provider: ExIcon.Providers.Lucide,
# The release version of the icon library.
version: "1.8.0",
# The destination path of the icon module that ExIcon will generate.
module_path: "lib/my_app_web/components/lucide.ex",
# The name of the generated module.
module_name: MyAppWeb.Components.Lucide,
# If supported by the provider, choose the style variants to generate
# Example: [:outline, :solid]
variants: [],
# SVG attributes to turn into component attributes, to override, or to
# add. Example: ["stroke", {"stroke-width", default: "1.5"}]
attrs: [],
# Whether the components accept the global HTML attributes. Can be a
# boolean or a keyword list with options to pass to `attr`.
global_attrs: false,
# Either list only the icons you want to generate, or set to `:all` to
# generate all available icons.
icons: ["arrow-left", "arrow-right"],
# Icon names to skip, which is mostly useful with `icons: :all`.
exclude: []
]
]
]
lucide is an arbitrary name that is currently only used for CLI output.
You can configure any number of icon providers. See t:ExIcon.options/0 for
all available options.
With your configuration in place, you can download the configured releases and generate a module with function components for every icon set with:
mix ex_icon.gen.icons
Downloaded releases are cached in your Mix cache folder (~/.cache/mix/ex_icon
on Linux, ~/Library/Caches/mix/ex_icon on macOS), so regenerating your icons
does not download the same release again. The exact path is printed after the
task ran.
Paths in the configuration file stay relative to the folder you run the task in.
Run mix help ex_icon.gen.icons for the available command line arguments.
Icon names
Each function component is named after its SVG file in snake case, so
arrow-left.svg becomes arrow_left. Names that start with a digit get an
icon_ prefix, since HEEx does not accept component names that start with a
digit. 1password.svg becomes icon_1password. Names that are reserved words
in Elixir get the same prefix, so end.svg becomes icon_end.
Icons with names that cannot be turned into function names are skipped and reported. If you listed such an icon in your configuration, the task fails instead.
Icon contents
ExIcon only keeps the SVG elements and attributes it knows. If an icon uses
anything else, such as a script element or an onclick attribute, ExIcon
skips the icon and prints the reason. If you listed such an icon in your
configuration, the task fails instead.
Attributes that load another document are not allowed. href, xlink:href, and
url() references in clip-path, color, fill, mask, stop-color and
stroke may only point at an ID in the same file. A style attribute may only
use known CSS functions, and may not contain a backslash.
ExIcon removes metadata elements and elements with a namespace prefix other
than svg. Such elements are commonly added by editors like Inkscape.
Attributes
ExIcon can optionally turn SVG attributes into function component attributes, override the values of the original SVG files, and add attributes that the original SVG files do not have.
For example, consider this original SVG:
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
>
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
And this configuration:
attrs: [
# component attribute, defaults to the value in the SVG file
"stroke",
# component attribute with a default value that overrides the value in the
# SVG file
{"stroke-width", default: "1.5"},
# component attribute with a limited set of values
{"stroke-linecap", values: ["square", "round"]},
# required attribute
{"class", required: true},
# optional component attribute that is omitted unless it is passed
{"stroke-dasharray", default: nil},
# sets a fixed attribute value without adding a component attribute
{"fill", fixed: "none"}
]
The generated function component will look like this:
attr :stroke, :string, default: "currentColor"
attr :stroke_width, :string, default: "1.5"
attr :stroke_linecap, :string, values: ["square", "round"], default: "round"
attr :class, :string, required: true
attr :stroke_dasharray, :string, default: nil
def arrow_left(assigns) do
~H"""
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke={@stroke}
stroke-width={@stroke_width}
stroke-linecap={@stroke_linecap}
class={@class}
stroke-dasharray={@stroke_dasharray}
fill="none"
aria-hidden="true"
>
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
"""
end
Note that if you generate a lot of icons, compilation times can increase substantially by adding component attributes.
Global attributes
Generated components only accept the attributes you configure. Set
global_attrs to also accept the global HTML attributes, such as id,
class, phx-click and data-*:
global_attrs: true
Instead of true, you can pass the default and include options of attr:
global_attrs: [default: %{"class" => "size-6"}, include: ["fill"]]
ExIcon writes the attributes you pass to a component before the ones of the SVG
file, so that yours take precedence. Both end up in the markup, and HTML keeps
the first of the two. <Lucide.arrow_left width="48" /> therefore renders with
a width of 48, even if the icon has one.
Variants
Some icon libraries ship the same icons in multiple styles. For example,
Heroicons has outlined and filled icons in several sizes. Select the ones you
need with the variants option:
heroicons: [
provider: ExIcon.Providers.Heroicons,
version: "2.2.0",
module_path: "lib/my_app_web/components/heroicons.ex",
module_name: MyAppWeb.Components.Heroicons,
variants: [:outline, :solid],
attrs: ["stroke", "stroke-width", "fill"],
icons: ["academic-cap", "bell"]
]
Each variant is generated into a separate module, with the variant appended to
module_name and module_path. In this example, ExIcon generates
MyAppWeb.Components.Heroicons.Outline into
lib/my_app_web/components/heroicons/outline.ex and
MyAppWeb.Components.Heroicons.Solid into
lib/my_app_web/components/heroicons/solid.ex. No module is written to the
configured module_path itself.
Without the option, ExIcon generates a single module from the variant that the
provider falls back to, which is :outline for Heroicons.
Local icon sets
You can also generate an icon module from a local folder with SVG files by using
the path option instead of provider and version:
custom: [
path: "assets/icons",
module_path: "lib/my_app_web/components/custom.ex",
module_name: MyAppWeb.Components.Custom,
icons: :all
]
The variants option is not supported for local folders.
Providers
Providers for specific icon libraries are based on the ExIcon.Provider
behaviour. The library currently supports these providers:
You can write a provider for any icon library that publishes its releases as a zip file. A provider tells ExIcon where to download a release, and where the SVG files are in it.
defmodule MyApp.Feather do
@behaviour ExIcon.Provider
@impl true
def release_url(version) do
"https://github.com/feathericons/feather/archive/refs/tags/v#{version}.zip"
end
@impl true
def svg_folder(version), do: "feather-#{version}/icons"
end
You can configure the module like any other provider in your configuration file:
feather: [
provider: MyApp.Feather,
version: "4.29.2",
module_path: "lib/my_app_web/components/feather.ex",
module_name: MyAppWeb.Components.Feather,
icons: :all
]
For a library that ships the same icons in several styles, implement the
optional c:ExIcon.Provider.variants/1 callback, which returns the folder of
each style. ExIcon.Providers.Heroicons is an example.
Versioning
ExIcon follows semantic versioning. The public API is:
- the format of the configuration file
- the
ExIcon.Providerbehaviour - the command line interface of
mix ex_icon.gen.icons - the shape of the generated components: one function component per icon, named
after the SVG file in snake case, with a
:stringattribute for every attribute configured withattrs
Everything else is internal, including all functions of the ExIcon module. The
generated modules only depend on Phoenix.Component, so upgrading ExIcon never
requires regenerating them.
Project status
ExIcon is actively maintained, but since it covers the use cases it was built for, you may not see frequent releases. Issues are fixed, and providers for further icon libraries are added when the need arises. There may well be use cases that ExIcon does not cover yet.
Contributing
Please open an issue or PR if you need additional options, features, or support for other icon libraries. Be sure to read the contributing guidelines.