Nov 16, 2021

Custom fonts with Phoenix and Tailwind CSS

Setting up custom fonts for use with Phoenix and Tailwind is pretty straight forward. It’s also pretty easy to miss a step and spend way too long trying to figure out why your fonts aren’t showing up… is it possible this is something I’ve learned from personal experience… sigh… yes, yes it is! In anycase, today we’ll setup a new Phoenix project and throw some custom fonts at it.

Let’s get at it!

Create a new project

We’ll need a Phoenix project for our fonts so let’s get that set up. I’m using version 1.6 of Phoenix, if you’re on an older version of Phoenix you might not have much luck with this guide, as it assumes we’re using esbuild for asset bundling, which replaced npm as of version 1.6 of Phoenix.

We also don’t need a database for our simple project… so it’s a --no-ecto kinda’ day.

Terminal
mix phx.new fonts --no-ecto

Select yes when asked to install dependencies.

Now we can change into the directory where the application was created.

Terminal
cd fonts

And let’s run the server just to make sure everything is working.

Terminal
mix phx.server

Navigating to http://localhost:4000/ should yield our default Phoenix application:

Great, now let’s add Tailwind CSS.

Add Tailwind

Now that Phoenix uses esbuild setting up Tailwind is a little different than before. However there is a great hex package that sets up everything for you.

This is what we’ll be using, so first we need to add the dependency to our mix.exs file.

/mix.exs
...
...
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"},
{:phx_gen_tailwind, "~> 0.1.3", only: :dev, runtime: false}
...
...

Note: npm is required in order to install Tailwind so make sure npm is available in your application directory, i.e.

Terminal
npm --version

With that out of the way we can grab our new dependency:

Terminal
mix deps.get

… and now just run the tailwind generator:

Terminal
mix phx.gen.tailwind

Select yes when asked to install the npm dependencies.

And that’s it! Simple!

Let’s update the index page to make sure Tailwind is working.

/lib/fonts_web/templates/page/index.html.heex
<div class="text-red-500 text-5xl text-center">Hi there</div>
<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
  ...

We just added a div with some Tailwind classes.

If we fire up the server we’ll see our styled Hi there text.

Terminal
mix phx.server

Perfect, onto the fonts!

Add some fonts

First step here is to download the actual font resources. If you’re familiar with Tailwind UI you’ll know they recommend using the Inter font family as a default font. So we’ll be doing that and also adding a decorative font (Lobster) that could possibly be used in place of a logo or something.

Setting up our font directories

We’ll be placing our fonts inside /assets/vendor/fonts, so let’s create directories for both fonts.

Terminal
mkdir -p assets/vendor/fonts/InterWeb
mkdir assets/vendor/fonts/Lobster

Next we need to download the font resources.

Download the fonts

The Inter font can be downloaded here. Just select the download button.

The Lobster font can be downloaded here. Select the “modern browsers” option and click the download button.

Now we can unzip the files and copy the relevant files to the asset directories we set up. In the case of the Inter font copy all the files in Inter Web to /assets/vendor/fonts/InterWeb.

With the Lobster font, just copy over the 2 files from the zip archive to /assets/vendor/fonts/Lobster.

Unlike the Inter font, the Lobster font doesn’t include a CSS file, so let’s create that now.

Terminal
touch assets/vendor/fonts/Lobster/lobster.css
/assets/vendor/fonts/Lobster/lobster.css
/* lobster-regular - latin */
@font-face {
  font-family: 'Lobster';
  font-style: normal;
  font-weight: 400;
  src: local(''),
       url('lobster-v23-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
       url('lobster-v23-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

That takes care of the font resources, now we need to link them up in our application.

Including the fonts in the application

First thing we’ll do is link to our font files. We’ll do this in the root layout.

/lib/fonts_web/templates/layout/root.html.heex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <%= csrf_meta_tag() %>
    <%= live_title_tag assigns[:page_title] || "Fonts", suffix: " · Phoenix Framework" %>
    <!-- add these 2 links -->
    <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/vendor/fonts/InterWeb/inter.css")}/>
    <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/vendor/fonts/Lobster/lobster.css")}/>
    <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
    <!-- update this link -->
    <script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/js/app.js")}></script>
  </head>
  <body>
    <header>
    ...
    ...

We’ve added a link for each font and updated the app.js path to be assets/js/app.js instead of assets/app.js.

Update the esbuild configuration

Now we need to update the args list in the esbuild section of the configuration file.

/config/config.exs
...
...

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.12.18",
  default: [
    args: ~w(js/app.js vendor/fonts/InterWeb/inter.css vendor/fonts/Lobster/lobster.css --bundle --loader:.woff2=file --loader:.woff=file --target=es2016 --outdir=../priv/static/assets),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

...
...

We’ve added references to the css files for our fonts (i.e. vendor/fonts/InterWeb/inter.css vendor/fonts/Lobster/lobster.css) and then also added a loader argument for the woff and woff2 font file types (i.e. --loader:.woff2=file --loader:.woff=file).

Update the Tailwind configuration

The final step is to update Tailwind.

/assets/tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  mode: 'jit',
  purge: [
    './js/**/*.js',
    '../lib/*_web/**/*.*ex'
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter var', ...defaultTheme.fontFamily.sans],
        lobster: ['Lobster']
      },
    },
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Here we’re setting the Inter font as the default font and making the Lobster font available to our application, Tailwind will create a font-lobster class we can reference.

We’re all set!

Trying it out

Let’s give it a go, we’ll update index.html.heex to include a new div with some text using the Lobster font.

/lib/fonts_web/templates/page/index.html.heex
<div class="text-red-500 text-5xl text-center">Hi there</div>
<div class="text-5xl font-lobster text-center">Lobster</div>
<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
  ...

Let’s fire up the server.

Terminal
mix phx.server

And voila, we got fonts batman!

The font-lobster class is working as expected and the default font is now the Inter font (you can verify this if you comment out the <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/vendor/fonts/InterWeb/inter.css")}/> line in root.html.heex and observe the subtle font change).

Summary

So serving custom fonts turns out to be pretty easy. To summarize, essentially all we need to do is:

  • Download the font resources.
  • Link to the resources in the root layout.
  • Update the esbuild config to load woff and woff2 files.
  • Update the tailwind config (obviously only if you’re using tailwind).

Today’s code is available on GitHub.

Thanks for reading and I hope you enjoyed the post!



Comment on this post!