This page looks best with JavaScript enabled

Hugo with Font Awesome

 ·  ☕ 10 min read

Font Awesome is an icon font. It’s used all over the place. I use it when developing editor tools for my Unity projects. I love it. It’s awesome.

I just started working on this site and I chose to build it with Hugo, which is a very popular open-source static site generator. So far, I like it a lot, but I was keenly interested in adding support for Font Awesome.

The Font Awesome docs provide numerous ways to get started, from kits to self-hosted setups, and using web fonts or SVG images with javascript. There are many, many articles that detail the advantages of SVG icons over font icons, so I’m only interested in utilizing SVGs.

The traditional setup involves loading in a JavaScript files on every page that will includes icons, and then using placeholder tags wherever you want icons to appear. JavaScript will replace those tags with SVGs once everything finishes loading.

A 2018 article by Nick Glabreath mentions some potential downsides to the traditional setup, such as extra network dependencies and potential processing delays. It also provides some great information on embedding SVGs directly in the page when the site is built, which avoids the extra network calls and delays at the expense of a slightly larger page load.

A more recent article by Micah R Ledbetter took what Galbreath presented and made a few enhancements, which I will build upon further.

Quick and Easy

Download Font Awesome

I have an active subscription to Font Awesome Pro, so I just downloaded the v5.15.4 release from their website, but you can download the latest release or the free icon set from the GitHub repository. The version I’m using contains 7,864 pro icons and 1,608 free icons.

Copy the SVGs

Some of the other articles have you place the SVG (scalable vector graphics) files inside your theme folder, however Hugo is designed for customization with a layer-like site generation workflow. This allows you to modify your site and override your theme without actually changing the theme code or folder structure. Here’s a short post illustrating how Hugo Theme Layering works. Ideally nothing should be changed inside the theme folder, and that includes adding your Font Awesome SVGs. So they’ll be added to the root instead.

The SVGs were extracted from the archive and placed in the assets folder, since they’re used like other assets and only accessed when the site is built. The hierarchy looks like this:


If you’re using the free version of Font Awesome, you may only have a few of those folders, such as solid and brands.

Add the partials

Processing the SVG

Create a partial file: <root>/layouts/partials/fontawesome.html

{{- $settings := partial "func/getFontAwesomeSettings.html" (dict "style" .style "arg1" .arg1 "arg2" .arg2) -}}
<span style="line-height:1em; vertical-align:middle;">
  {{- $fname:=print "/assets/svg/" .style "/" .icon ".svg" -}}
  {{- if (fileExists $fname) -}}
    {{- $svg := readFile $fname -}}
    {{- $svg = replace $svg " 512\"><!--" (print " 512\" style=\"height:" $settings.size "; width:" $settings.size "\"><!--") -}}
    {{- $svg = replaceRE "<!--[^>]*-->" "" $svg -}}
    {{- if eq .style "duotone" -}}
      {{- $svg = replace $svg "class=\"fa-primary\"/>" (print "class=\"fa-primary\" style=\"fill:" $settings.primaryColor "; opacity:" $settings.primaryOpacity "\"/>") -}}
      {{- $svg = replace $svg "class=\"fa-secondary\"/>" (print "class=\"fa-secondary\" style=\"fill:" $settings.secondaryColor "; opacity:" $settings.secondaryOpacity "\"/>") -}}
      {{- $svg = replace $svg "<defs><style>.fa-secondary{opacity:.4}</style></defs>" "" -}}
    {{- else -}}
      {{- $svg = replace $svg "<path" (print "<path fill=\"" $settings.primaryColor "\" opacity=\"" $settings.primaryOpacity "\"") -}}
    {{- end -}}
    {{- $svg | safeHTML -}}
  {{- else -}}
    <span class="sc-fontawesome-missing" title="Could not find &quot;{{.icon}}&quot; icon with &quot;{{.style}}&quot; style">&#xFFFD;</span>
    {{- warnf "Could not find \"%s\" icon with \"%s\" style." .icon .style -}}
  {{- end -}}
</span>{{- "" -}}

Note that this partial differs from Ledbetter’s in a few ways.

  1. This partial was placed outside the theme, so the theme folder path is no longer needed in the $fname variable.
  2. The icon style and name are passed to the partial separately and accessed using .style and .icon, respectively.
  3. Optional size and color (with opacity) settings may now be passed through the shortcodes, and the input is validated with a call to the getFontAwesomeSettings partial, which returns values that are accessed with $settings.
  4. The size is applied directly to <svg> using inline styles, which seems to improve layout and sizing with adjacent text.
  5. There’s now special handling of duotone icons, which have primary and secondary colors and opacities, and those need to be applied to the appropriate <path> in the SVG. The <defs><style> combo is also removed, as its no longer needed.
  6. There’s a check to see if the file exists before rendering it. If it doesn’t exist, a build warning will be logged and the replacement glyph (�) will be embedded into the page instead. The span wrapping the replacement character also has a tooltip that includes the icon and style names being used, which may be useful for debugging the missing icon. Most importantly, this check allows Hugo to successfully rebuild the page, otherwise the build will fail when readFile fails to load the missing SVG file.

Parsing Settings

Create a partial file: <root>/layouts/partials/func/getFontAwesomeSettings.html

Note that I placed this in the func sub-directory, because this partial will return a value, and I like to keep partials that return values separate from those that don’t.

{{ $lengthPattern := "^[\\d\\.]+(?i)(?:cm|in|mm|pc|pt|px|q|%|ch|em|ex|lh|rem|vh|vmax|vmin|vw)(?-i)$" }}

{{ $data := newScratch }}
{{ $data.Set "size" "1em" }}
{{ $data.Set "primaryColor" "currentColor" }}
{{ $data.Set "primaryOpacity" "1.0" }}
{{ $data.Set "secondaryColor" "currentColor" }}
{{ $data.Set "secondaryOpacity" "0.4" }}

{{ $args := slice .arg1 .arg2 }}
{{ range $args }}
  {{ $length := findRE $lengthPattern . }}
  {{ with $length }}
    {{ $data.Set "size" (delimit $length "") }}
  {{ else }}
    {{ with . }}
      {{ $parts := split . ";" }}
      {{ with (index $parts 0) }}
        {{ $data.Set "primaryColor" ((partial "func/getFontAwesomeColorOpacity.html" (slice . 0)) | default "currentColor") -}}) }}
        {{ $data.Set "primaryOpacity" ((partial "func/getFontAwesomeColorOpacity.html" (slice . 1)) | default "1.0") -}}) }}
      {{ end }}
      {{ with (index $parts 1) }}
        {{ $data.Set "secondaryColor" ((partial "func/getFontAwesomeColorOpacity.html" (slice . 0)) | default "currentColor") -}}) }}
        {{ $data.Set "secondaryOpacity" ((partial "func/getFontAwesomeColorOpacity.html" (slice . 1)) | default "0.4") -}}) }}
      {{ end }}
    {{ end }}
  {{ end }}
{{ end }}

{{- return (dict "size" ($data.Get "size")  "primaryColor" ($data.Get "primaryColor")  "primaryOpacity" ($data.Get "primaryOpacity")  "secondaryColor" ($data.Get "secondaryColor")  "secondaryOpacity" ($data.Get "secondaryOpacity") ) -}}

This may look a bit complicated, but that’s because either or both of the size and color/opacity settings can be provided and in any order, which simplifies the shortcode usage and makes it more flexible. Also, the color data is a compound string containing up to two color and two opacity values, which are parsed and separated before the values are returned.

I’ll run through it quickly.

  1. A regex pattern is used to identify the size argument, and all length types defined in the CSS specification are included. I only tested with em, rem, and px, but they should all work as expected.
  2. A scratch is used to store potential results as the partial arguments are being processed. Default values are established and will be used unless the setting is overridden by an argument.
  3. Both arguments are examined, and if the size is identified, it’s used, otherwise color parsing is performed. Parsing calls the getFontAwesomeColorOpacity partial, which takes a slice and returns a string, and if parsing fails a default value will be applied.
  4. The results are returned in a dict using named parameters.

Parsing Color and Opacity

Create a partial file: <root>/layouts/partials/func/getFontAwesomeColorOpacity.html

{{ $result := 0 }}
{{ $parts := split (index . 0) "/" }}
{{ with (index $parts (index . 1)) }}
  {{ $result = trim . " \t\n" }}
{{ end }}

{{- return $result -}}

This partial is really simple. It takes a slice containing a string and an index. The string may contain both color and opacity data, so it’s split into color and opacity. The index just tells the function which setting is requested, and if it exists, it’ll be returned, otherwise the function will return 0 as an failure condition.

That’s it! On to the shortcodes!

Add the shortcodes

For each shortcode you want to use, create a file at: <root>/layouts/shortcodes/. I created a shortcode for each of the Font Awesome styles (brands, duotone, light, regular, solid). If you’re using the free version, you probably won’t need them all.


Here’s what fas.html is looks like:

{{ partial "fontawesome.html" (dict "style" "solid" "icon" ( .Get 0 ) "arg1" ( .Get 1 ) "arg2" ( .Get 2 )) }}

The only thing that differs in fab.html is that the style parameter was changed to brands:

{{ partial "fontawesome.html" (dict "style" "brands" "icon" ( .Get 0 ) "arg1" ( .Get 1 ) "arg2" ( .Get 2 )) }}

Be sure to use duotone, light, and regular in the other files, if you create them.

Shortcode Usage


{{< <style-shortcode> <icon> [color] [size] >}}


<style-shortcode>One of the Font Awesome shortcodes created earlier: fab, fad, fal, far, or fas.
<icon>A Font Awesome icon code such as rocket-launch.

A color pattern, which may include a single color value using standard CSS color notation: [color]
 • orange
 • #ffa500
 • rgb(255, 165, 0)
 • hsl(39, 100%, 50%)

A color and an opacity: [color][/<opacity>]
 • orange / 50%
 • #ffa500 / 0.5

Two colors with optional opacity: [color][/<opacity>][;[color][/<opacity>]
 • orange / 50% ; blue / 100%
 • #ffa500 / 0.5 ; #00f / 1

To set opacity without affecting color, leave the color value blank or set it to currentColor:
 • / 50%
 • currentColor / 0.5

To set secondary color without affecting the primary, leave the primary color value blank or set it to currentColor:
 • ; blue
 • currentColor ; #00f

If the value includes any non-alphanumeric characters, wrap it in quotes.

The size value using standard CSS length notation.

If the value includes any non-alphanumeric characters, wrap it in quotes.


To use in markdown content, just use one of the new shortcodes followed by the icon name.

fab brands
fad duotone
fal light
far regular
fas solid

You may specify a color after the icon name using standard CSS color notation, such as a hex, rgb, or hsl value or a color keyword.

{{< fas jack-o-lantern DarkOrange >}}
{{< fas jack-o-lantern "#ff8c00" >}}
{{< fas jack-o-lantern "rgb(255, 140, 0)" >}}
{{< fas jack-o-lantern "hsl(33, 100%, 50%)" >}}

The color opacity may be changed by appending a / and a numeric or percentage value. Whitespace surrounding the / is ignored.

Do not specify opacity directly in the color value, like you can when using a 4-byte hex value or rgba or hsla with alpha channels. Append it instead.

#6699FF80 (4 bytes)
rgba(102 153 255 / 50%)
hsla(220 100% 70% / 50%)

#6699FF / 50%
rgb(102 153 255) / 50%
hsl(220 100% 70%) / 50%

{{< fas jack-o-lantern "DarkOrange/0.5" >}}
{{< fas jack-o-lantern "DarkOrange/50%" >}}
{{< fas jack-o-lantern "DarkOrange / 50%" >}}
{{< fas jack-o-lantern "rgb(255, 140, 0) / 50%" >}}

The size may also be specified using standard CSS length notation.

{{< fas jack-o-lantern 3rem >}}

The order of the optional color and size parameters doesn’t matter, as long as they follow the icon parameter.

{{< fas jack-o-lantern DarkOrange 3rem >}}
{{< fas jack-o-lantern 3rem DarkOrange >}}

Duotone icons have a slightly different color syntax, because they have both a primary and secondary color. Just separate the colors with a ;. Whitespace surrounding the ; is ignored.

By default the secondary color has an opacity of 40%, so you may want to override that as well.

{{< fad jack-o-lantern "yellow;DarkOrange/1" 3rem >}}
{{< fad jack-o-lantern "yellow ; DarkOrange / 100%" 3rem >}}

Have fun with icons!

Share on
Support the author with

Michael Ryan
Michael Ryan
<div><span>Video game developer</span> /<br /><span>Retro computing fanatic</span></div>