Adding material symbols to a Phoenix project

Ambrose MungaiAmbrose Mungai
3 min read

Using Material Symbols with Phoenix and Tailwind CSS

Material Symbols is the variable font version of Google’s Material Icons. Unlike traditional icon fonts, variable fonts allow a single file to contain multiple stylistic variations—significantly reducing the number of assets you need to manage.

Material Symbols replaces the now-deprecated Material Icons fonts and is actively maintained with regular updates.

Why Use Material Symbols in Phoenix?

By default, new Phoenix projects include a set of built-in components, including an icon/1 component that renders Heroicons. While Heroicons are elegant and often sufficient, Material Symbols offer a far larger collection. They're widely adopted by UI designers, which means many mockups and design systems use them by default.

To preserve visual fidelity with the design, substituting icons between sets (like Heroicons and Material Symbols) is not ideal. Having Material Symbols available directly in your Phoenix app makes it easier to match designs precisely.

Material Symbols Overview

Material Symbols are based on 24px icons and come in three styles:

  • Outlined
  • Rounded
  • Sharp

These are variable fonts with four axes of customization:

  • Optical Size: Default is 24
  • Weight: Default is 400 (Regular)
  • Grade: Default is 0
  • Fill: Default is 0

For simplicity, we'll use the default values and only include the 400 weight icons.

Installing Material Symbols via Dependency

To avoid manually downloading and maintaining icon files, we can pull them directly from GitHub using a sparse checkout in mix.exs. This setup fetches only the necessary svg/400 folder, avoids compiling the dependency, and reduces disk usage by limiting Git history.

Add the following to your mix.exs dependencies:

{:material_icons,
  github: "marella/material-symbols",
  sparse: "svg/400",
  app: false,
  compile: false,
  depth: 1
}

Then run:

mix deps.get

Extending the icon/1 Component

Phoenix comes with a built-in icon/1 component for Heroicons. We’ll add support for Material Symbols by introducing a new clause that matches icon names with a material- prefix.

In core_components.ex, add this function:

def icon(%{name: "material-" <> _} = assigns) do
  ~H"""
  <span class={[@name, @class]} aria-hidden="true" {@rest} />
  """
end

This allows you to use Material Symbols like so:

<.icon name="material-home" />

Configuring Tailwind to Recognize Material Symbols

Next, we need Tailwind to generate CSS classes for the Material Symbols. We’ll scan the SVG files in the deps/material_icons directory and create utility classes based on their filenames.

Update your tailwind.config.js with the following plugin:

plugin(function({ matchComponents, theme }) {
  const path = require("path");
  const fs = require("fs");

  const iconsDir = path.join(__dirname, "../deps/material_icons/svg/400");
  const values = {};

  const styles = [
    ["", "outlined"],
    ["_rounded", "rounded"],
    ["_sharp", "sharp"],
  ];

  styles.forEach(([suffix, dir]) => {
    fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
      let name = path.basename(file, ".svg") + suffix;
      name = name.replaceAll("_", "-");

      values[name] = {
        name,
        fullPath: path.join(iconsDir, dir, file),
      };
    });
  });

  matchComponents({
    "material": ({ name, fullPath }) => {
      let content = fs.readFileSync(fullPath, "utf8").replace(/\r?\n|\r/g, "");
      content = content.replace(' width="48" height="48"', "");

      const size = theme("spacing.6");

      return {
        [`--material-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
        "-webkit-mask": `var(--material-${name})`,
        "mask": `var(--material-${name})`,
        "mask-repeat": "no-repeat",
        "background-color": "currentColor",
        "vertical-align": "middle",
        "display": "inline-block",
        "width": size,
        "height": size,
      };
    }
  }, { values });
})

With this setup, Tailwind will generate classes like material-home based on your icons. You can then use them as utility classes in your components.

Keeping Icons Up to Date

Since the Material Symbols repository is frequently updated via GitHub Actions, your app can stay current with new icons by simply running:

mix deps.update material_icons

Conclusion

Integrating Material Symbols with Phoenix and Tailwind gives you access to a vast and regularly updated icon set that aligns with modern UI design standards. By automating the dependency, icon rendering, and Tailwind class generation, you streamline both development and design fidelity—ensuring your front end looks just like the mockups.

0
Subscribe to my newsletter

Read articles from Ambrose Mungai directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ambrose Mungai
Ambrose Mungai