Phoenix ships with HeroIcons, which is great for out-of-the-box free icons. FontAwesome also has a fantastic set of free icons, as well as a stellar set of paid ones. It's easy to configure Phoenix/Tailwind to use FontAwesome instead of HeroIcons, or use them both together.
FontAwesome as a Dependency
As a starting point, lets assume a brand new Phoenix application generated with mix phx.new
.
Add the font awesome package as an NPM dependency:
cd assets
npm init -y
npm install --save @fortawesome/fontawesome-free
Configuring Tailwind
With FontAwesome installed and ready, we need to make changes to the tailwind.config.json
. There is an array that should contain several plugin declarations. We can add this one just below the Hero Icon declaration, or replace it if they are no longer needed.
plugin(function ({ matchComponents, theme }) {
let iconsDir = path.join(
__dirname,
"node_modules/@fortawesome/fontawesome-pro/svgs",
);
let values = {};
let icons = [
["", "/regular"],
["-duotone", "/duotone"],
["-light", "/light"],
["-solid", "/solid"],
["-thin", "/thin"],
["-brands", "/brands"],
];
icons.forEach(([suffix, dir]) => {
fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => {
let name = path.basename(file, ".svg") + suffix;
values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
});
});
matchComponents(
{
fa: ({ name, fullPath }) => {
let content = fs
.readFileSync(fullPath)
.toString()
.replace(/\r?\n|\r/g, "");
let size = theme("spacing.6");
if (name.endsWith("-mini")) {
size = theme("spacing.5");
} else if (name.endsWith("-micro")) {
size = theme("spacing.4");
}
return {
[`--fa-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
"-webkit-mask": `var(--fa-${name})`,
mask: `var(--fa-${name})`,
"mask-repeat": "no-repeat",
"background-color": "currentColor",
"vertical-align": "middle",
display: "inline-block",
width: size,
height: size,
"mask-size": "contain",
"mask-position": "center",
};
},
},
{ values },
);
})
assets/tailwind.config.json
Modifying the Icon Component
Inside core_components.ex
, there is an icon/1
function that provides the icon component for views. By default, it supports Hero Icons. We can modify this to also support FontAwesome icons. We will also add a spin
attribute to the component to enable FA's spin animation.
attr :name, :string, required: true
attr :class, :string, default: nil
attr :spin, :boolean, default: false
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
"""
end
def icon(%{name: "fa-" <> _} = assigns) do
~H"""
<span class={[@name, @spin && ["animate-spin"], @class]} />
"""
end
lib/project_name/components/core_components.ex
Trying It Out
We will add a spinning user icon to the top of the home page as a test. Add the following code to the top of home.html.heex
.
<.icon name="fa-user" spin />
lib/project_name/controllers/page_html/home.html.heex
Now load up the site with mix phx.server
, browse to http://localhost:4000
, and the icon should be visible at the top of the page.
Using FontAwesome Pro
If you have a FontAwesome Pro subscription, the steps are similar.
Create a .npmrc File
You need a .npmrc
file in your assets
path with the authorization token for your FontAwesome access.
@fortawesome:registry=https://npm.fontawesome.com/
//npm.fontawesome.com/:_authToken=<<YourTokenHere>>
assets/.npmrc
Font Awesome Dependency
Instead of installing the free font awesome library, install the pro version:
cd assets
npm init -y
npm install --save @fortawesome/fontawesome-pro
Configuring Tailwind
The tailwind configuration is similar, but with more styles and different paths:
plugin(function ({ matchComponents, theme }) {
let iconsDir = path.join(
__dirname,
"node_modules/@fortawesome/fontawesome-pro/svgs",
);
let values = {};
let icons = [
["", "/regular"],
["-duotone", "/duotone"],
["-light", "/light"],
["-solid", "/solid"],
["-thin", "/thin"],
["-brands", "/brands"],
];
icons.forEach(([suffix, dir]) => {
fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => {
let name = path.basename(file, ".svg") + suffix;
values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
});
});
matchComponents(
{
fa: ({ name, fullPath }) => {
let content = fs
.readFileSync(fullPath)
.toString()
.replace(/\r?\n|\r/g, "");
let size = theme("spacing.6");
if (name.endsWith("-mini")) {
size = theme("spacing.5");
} else if (name.endsWith("-micro")) {
size = theme("spacing.4");
}
return {
[`--fa-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
"-webkit-mask": `var(--fa-${name})`,
mask: `var(--fa-${name})`,
"mask-repeat": "no-repeat",
"background-color": "currentColor",
"vertical-align": "middle",
display: "inline-block",
width: size,
height: size,
"mask-size": "contain",
"mask-position": "center",
};
},
},
{ values },
);
})
assets/tailwind.config.json
The sourcecode for this tutorial is available on GitHub