Hugo with Font Awesome

 ·   ·  ☕ 10 min read
🏷️
This page looks best with JavaScript enabled

Font Awesome is an icon font widely used across various websites, applications, and projects. I’ve been utilizing it for years to develop editor tools for my Unity projects. It’s truly awesome.

One of the first decisions I made as I started building this site with Hugo, a popular open-source static site generator, was to integrate Font Awesome to handle my icon needs. I specifically wanted to use SVGs due to their numerous advantages over traditional font icons. Many articles detail the advantages of SVG icons over font icons, so I won’t repeat those here.

The Font Awesome documentation offers various setup methods, from kits to self-hosted options, using web fonts or SVGs with JavaScript. However, I prefer embedding SVGs directly to avoid extra network dependencies and processing delays, as highlighted in a 2018 article by Nick Glabreath. This method results in slightly larger page loads but more efficient performance.

A more recent article by Micah R Ledbetter builds on Galbreath’s work with additional enhancements, which I will further develop.

Quick and Easy

Download Font Awesome

I have an active subscription to Font Awesome Pro, so I downloaded the v5.15.4 release from their website. You can download the latest release or the free icon set from the GitHub repository. My version includes 7,864 pro icons and 1,608 free icons.

Copy the SVGs

Some articles suggest placing SVG (scalable vector graphics) files inside your theme folder. However, Hugo’s layer-like site generation workflow allows customization without altering the theme code or folder structure. Ideally, nothing should be changed inside the theme folder, so we don’t want to place the Font Awesome SVGs there.

Instead, store the SVGs under assets in the the project root. These files are treated like other assets and only used when the site is generated. The hierarchy looks like this:

<root>
assets
svg
brands
duotone
light
regular
solid

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

Add the partials

Processing the SVG

Create a partial file:

<root>/layouts/partials/fontawesome.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{{- $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 from the SVG, as its no longer needed and results in a smaller image file size.

  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.

    If you actually want the build to fail when a missing icon is detected, replace the warnf with an errorf call. That will still stop the build, but produce an error that includes the missing icon information.

Parsing Settings

Create a new partial file in the func sub-directory, because this partial will return a value, and it helps to keep partials that return values separate from those that don’t.

<root>/layouts/partials/func/getFontAwesomeSettings.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{{ $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" }}
{{ $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") -}}) }}
      {{ 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 seem complex because both the size and color/opacity settings can be provided in any order. This flexibility simplifies shortcode usage. Additionally, the color data is a compound string containing up to two color and two opacity values, which are parsed and separated before being 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 another func partial for separating color and opacity components.

<root>/layouts/partials/func/getFontAwesomeColorOpacity.html
1
2
3
4
5
6
7
{{ $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.

<root>
layouts
shortcodes
fab.html
fad.html
fal.html
far.html
fas.html

Start with the shortcode that will handle Font Awesome solid (fas) icons:

<root>/layouts/shortcodes/fas.html
1
{{ partial "fontawesome.html" (dict "style" "solid" "icon" ( .Get 0 ) "arg1" ( .Get 1 ) "arg2" ( .Get 2 )) }}

To handle the brand icons, just change the style parameter to brands in the next shortcode.

<root>/layouts/shortcodes/fab.html
1
{{ partial "fontawesome.html" (dict "style" "brands" "icon" ( .Get 0 ) "arg1" ( .Get 1 ) "arg2" ( .Get 2 )) }}

If you’re using the Pro version of Font Awesome, be sure also create the fad, fal, and far shortcode files with the duotone, light, and regular styles, respectively.

Shortcode Usage

Syntax

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

Parameters

NotationDescription
<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.
[color]

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.
[size]

The size value using standard CSS length notation.

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

Examples

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!

Elevate your Font Awesome game!

Check out Using <use> with Font Awesome SVGs for advanced SVG optimization techniques, reducing load times, and boosting performance.

End of Line.