Stencil

Compile-time SVG inliner for Phoenix components.

Stencil reads SVG files from a directory at compile time and embeds them into a Phoenix component you can call from HEEx. The running app performs no disk I/O because the raw SVG bytes are baked into the module's beam file. New or changed files re-compile automatically when an SVG changes on disk.

This is a port of Lucky Framework's implementation.

Installation

Add stencil to your mix.exs:

def deps do
[
{:stencil, "~> 0.1.0"}
]
end

Usage

Put your SVGs under priv/svg/ in your app, then create a thin wrapper module that uses Stencil.Component. The conventional location is lib/my_app_web/components/inline_svg.ex:

defmodule MyAppWeb.InlineSVG do
use Stencil.Component
end

To make <.inline_svg> available in every HEEx template, import the wrapper inside MyAppWeb's html_helpers (in lib/my_app_web.ex) alongside MyAppWeb.CoreComponents:

defp html_helpers do
quote do
# ...
import MyAppWeb.CoreComponents
import MyAppWeb.InlineSVG
# ...
end
end

Now call it from any controller view, LiveView, or component template:

<.inline_svg path="check" />
<.inline_svg path="arrow-right" class="icon" />
<.inline_svg path="icons/star" />
<.inline_svg path="logo" strip_styling={false} />

The path attribute is the file name (or nested path) under priv/svg, without the .svg extension.

Options

use Stencil.Component, dir: "priv/icons"

Styling

By default, Stencil strips the SVG's own class, fill, stroke, stroke-width, and style attributes so page CSS controls the visual. Any attributes you pass to <.inline_svg> are forwarded onto the wrapping <svg> tag:

<.inline_svg path="check" class="w-6 h-6 text-emerald-500" />

Pass strip_styling={false} to keep the file's original attributes:

<.inline_svg path="logo" strip_styling={false} />

Stencil also adds a marker attribute so you can target inlined SVGs from CSS:

How it works

use Stencil.Component walks the configured directory at macro-expansion time and generates one function head per SVG:

defp stencil_raw_svg("check", true), do: "<svg …>…</svg>"

Each file is registered as an @external_resource, so the module re-compiles when any SVG changes. A __mix_recompile__?/0 callback also detects added or removed files.

License

MIT.