Skip to content

Instantly share code, notes, and snippets.

@greven
Last active April 14, 2025 19:57
Show Gist options
  • Save greven/36077217806ddd42989387a212a8c8db to your computer and use it in GitHub Desktop.
Save greven/36077217806ddd42989387a212a8c8db to your computer and use it in GitHub Desktop.
Phoenix CSS Icons
@doc """
Renders an icon.
Supports three icon libraries:
- [Heroicons](https://heroicons.com) - prefixed with "hero-"
- [Lucide](https://lucide.dev) - prefixed with "lucide-"
- [Simple Icons](https://simpleicons.org) - prefixed with "si-"
You can customize the size and colors of the icons by setting
width, height, and background color classes.
Icons are extracted from their respective directories and bundled within
your compiled app.css by the plugins in `assets/vendor/`.
## Examples
<.icon name="hero-x-mark-solid" />
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
<.icon name="lucide-github" />
<.icon name="si-github" class="size-6" />
<.icon name="si-elixir" class="size-5 text-purple-600" />
"""
attr :name, :string, required: true
attr :class, :string, default: "size-4"
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
"""
end
def icon(%{name: "lucide-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
"""
end
def icon(%{name: "si-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
"""
end
// Add lucide.js to assets/vendor
const plugin = require('tailwindcss/plugin');
const fs = require('fs');
const path = require('path');
module.exports = plugin(function ({ matchComponents, theme }) {
let iconsDir = path.join(__dirname, '../../deps/lucide_icons/icons');
let values = {};
fs.readdirSync(iconsDir).forEach((file) => {
if (file.endsWith('.svg')) {
let name = path.basename(file, '.svg');
values[name] = { name, fullPath: path.join(iconsDir, file) };
}
});
matchComponents(
{
lucide: ({ name, fullPath }) => {
let content = fs
.readFileSync(fullPath)
.toString()
.replace(/\r?\n|\r/g, '');
// Clean up SVG
content = content
.replace(/width="24"/g, '')
.replace(/height="24"/g, '')
.replace(/stroke-width="2"/g, 'stroke-width="1.5"')
.replace(/stroke="[^"]+"/g, 'stroke="currentColor"');
content = encodeURIComponent(content);
let size = theme('spacing.6'); // 1.5rem / 24px by default
return {
[`--lucide-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
'-webkit-mask': `var(--lucide-${name})`,
mask: `var(--lucide-${name})`,
'mask-repeat': 'no-repeat',
'background-color': 'currentColor',
'vertical-align': 'middle',
display: 'inline-block',
width: size,
height: size,
};
},
},
{ values }
);
});
# ...
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.5",
sparse: "optimized",
app: false,
compile: false,
depth: 1},
{
:lucide_icons,
github: "lucide-icons/lucide",
tag: "0.487.0",
sparse: "icons",
app: false,
compile: false,
depth: 1
},
{:simple_icons,
github: "simple-icons/simple-icons",
tag: "14.12.1",
sparse: "icons",
app: false,
compile: false,
depth: 1
},
# ...
// Add simple-icons.js to assets/vendor
const plugin = require('tailwindcss/plugin');
const fs = require('fs');
const path = require('path');
module.exports = plugin(function ({ matchComponents, theme }) {
let iconsDir = path.join(__dirname, '../../deps/simple_icons/icons');
let values = {};
fs.readdirSync(iconsDir).forEach((file) => {
if (file.endsWith('.svg')) {
let name = path.basename(file, '.svg');
values[name] = { name, fullPath: path.join(iconsDir, file) };
}
});
matchComponents(
{
si: ({ name, fullPath }) => {
let content = fs
.readFileSync(fullPath)
.toString()
.replace(/\r?\n|\r/g, '');
content = content.replace(/fill="[^"]+"/g, 'fill="currentColor"');
content = encodeURIComponent(content);
let size = theme('spacing.5');
return {
[`--si-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
'-webkit-mask': `var(--si-${name})`,
mask: `var(--si-${name})`,
'mask-repeat': 'no-repeat',
'background-color': 'currentColor',
'vertical-align': 'middle',
'mask-size': 'contain',
'mask-position': 'center',
display: 'inline-block',
width: size,
height: size,
};
},
},
{ values }
);
});
@greven
Copy link
Author

greven commented Apr 12, 2025

The Phoenix generator includes Heroicons by default, but while Heroicons
is a really nice set it isn't very expansive. Let's include Lucide Icons
and Simple Icons for brand icons.

Like the included Heroicons, they won't take space on the bundled CSS file as Tailwind will only include the icons used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment