Hugo is written in Go, but a Hugo site is not a Go program: templates cannot import packages, and stock Hugo cannot load third-party Goldmark extensions. Integration therefore happens at build time. The swarm-icons CLI prepares sanitized SVG files, and Hugo inlines them with a small partial.
What works and what does not
- The hugo-swarm-icons module (recommended): a ready-made partial and shortcode that inline CLI-exported icons and port the fluent manipulation API (resize, rotate, flip, colors, opacity, title) to Hugo templates. Covered next.
- Hand-rolled build-time assets: export any Iconify set with the CLI into the site's
assets/directory, then inline the files with your own partial or shortcode. This is the no-module fallback, covered after the module. - Pre-generated sprite sheets: run a small Go program before
hugoto produce a sprite partial, useful for icon-heavy themes. - The Goldmark submodule does not apply: Hugo enables only its own built-in Goldmark extensions and has no plugin mechanism for third-party ones, so the
:icon[prefix:name]Markdown syntax cannot be used in stock Hugo. A shortcode provides equivalent authoring.
Recommended: the hugo-swarm-icons module
The hugo-swarm-icons module packages everything below so you do not have to build it by hand, and adds what a hand-rolled partial lacks: the library's manipulation API, reimplemented in Hugo templates.
[[module.imports]]path = "github.com/frostybee/hugo-swarm-icons"hugo mod get github.com/frostybee/hugo-swarm-iconsThen, after exporting icons as described in the next section, the icon shortcode works in any content file, and the swarm-icon/icon.html partial in any template. Each parameter mirrors a fluent method (Size, Rotate, Flip, Fill, Stroke, Opacity, Class, Title), including viewBox-aware width/height derivation and accessibility defaults. A missing icon fails the build with the exact swarm-icons icon export command needed to fix it.
The simplest form takes just a prefix:name:
{{< icon "tabler:home" >}}Hugo shortcodes accept either positional or named parameters, never both in one call, so every example below switches to the named icon= form.
Sizing
size sets width and height to the same value. width or height alone derives the other side from the viewBox aspect ratio, preserving CSS units, so a non-square icon never distorts. "auto" restores the viewBox dimensions, and "unset" removes both attributes so CSS can take over:
{{< icon icon="tabler:home" size="32" >}}{{< icon icon="tabler:star" width="1.5em" >}}{{< icon icon="tabler:star" width="unset" class="css-sized-icon" >}}Colors and opacity
fill, stroke, stroke-width, and opacity set the corresponding attribute on the root element. Since most sets draw with currentColor, a style with a CSS color recolors an icon without touching its markup:
{{< icon icon="tabler:star" fill="goldenrod" >}}{{< icon icon="tabler:heart" stroke="rebeccapurple" stroke-width="1.5" >}}{{< icon icon="tabler:star" opacity="0.5" >}}{{< icon icon="tabler:arrow-up" style="color: red" >}}Rotation and flips
rotate takes degrees; flip takes h, v, or both. Used together they collapse into a single transform declaration (rotate applied first), and both append to any style you pass rather than overwriting it:
{{< icon icon="tabler:arrow-up" rotate="45" >}}{{< icon icon="tabler:arrow-up" flip="h" >}}{{< icon icon="tabler:arrow-up" rotate="90" flip="both" >}}{{< icon icon="tabler:arrow-up" style="color: red" rotate="180" >}}Classes and accessible labels
class space-concatenates onto any class already on the file. Icons are decorative by default (aria-hidden="true", focusable="false"); passing title switches to labeled mode, prepending an escaped <title> element and setting role="img":
{{< icon icon="tabler:home" class="nav-icon large" >}}{{< icon icon="tabler:heart" title="Add to favorites" >}}In templates: the partial and attrs
The partial accepts a bare name or a dict with every parameter above, plus one extra: attrs, a dict of arbitrary attributes applied at highest precedence. Use it for id, data-*, or ARIA attributes (aria-label also switches the icon to labeled mode):
{{ partial "swarm-icon/icon.html" "tabler:home" }}{{ partial "swarm-icon/icon.html" (dict "icon" "tabler:heart" "size" 20 "fill" "currentColor" "class" "icon icon-heart" "rotate" 15 "title" "Favorite") }}{{ partial "swarm-icon/icon.html" (dict "icon" "tabler:home" "attrs" (dict "id" "home-icon" "data-nav" "true" "aria-label" "Home")) }}The module README has the full parameter reference. The module covers rendering and manipulation only: runtime features such as aliases, fallback icons, chain providers, and on-demand API fetching have no template equivalent, as detailed in the module's scope and limitations. The rest of this page documents the manual approach, useful if you prefer no extra module dependency or need something the module does not cover.
Export icons into the site
Run these from the Hugo site root:
go install github.com/frostybee/go-swarm-icons/cmd/swarm-icons@latestswarm-icons json download tablerswarm-icons icon export tabler home star heart arrow-up brand-github --dest assets/icons/tablerjson download writes resources/json/tabler.json, which is exactly where icon export looks by default. Each exported file has passed through the library's nine-stage sanitization pipeline, so it is safe to inline as-is. Note that exported files carry no ARIA attributes: export bypasses the renderer's ARIA-injection step, so whatever inlines the file (the hugo-swarm-icons module does this automatically, a manual partial must do it itself) is responsible for adding aria-hidden or role.
Re-run swarm-icons json update followed by the same export command to pick up upstream icon changes.
Inline with a partial
Create layouts/partials/icon.html:
{{- $parts := split . ":" -}}{{- $path := printf "icons/%s/%s.svg" (index $parts 0) (index $parts 1) -}}{{- with resources.Get $path -}} {{- .Content | safeHTML -}}{{- else -}} {{- errorf "icon.html: no file for %q under assets/icons" . -}}{{- end -}}Use it anywhere in a template:
<a href="/">{{ partial "icon.html" "tabler:home" }} Home</a>The partial fails the build with a clear message when an icon name has a typo, which is the behavior you want in CI.
Use in Markdown with a shortcode
Create layouts/shortcodes/icon.html with a single line:
{{ partial "icon.html" (.Get 0) }}Then reference icons from any content file:
Press the {{< icon "tabler:star" >}} button to favorite a post.This mirrors what the Goldmark extension provides for Go-native Markdown pipelines.
With the module installed instead, the shortcode has the same name and the same bare form, so content files stay identical, but sizing, rotation, flips, colors, and titles become available as parameters right in Markdown:
Press the {{< icon "tabler:star" >}} button to favorite a post.{{< icon icon="tabler:star" size="32" fill="goldenrod" >}}{{< icon icon="tabler:arrow-up" size="20" rotate="45" style="color: #06c" >}}{{< icon icon="tabler:heart" flip="h" title="Add to favorites" >}}The hand-rolled one-liner deliberately stops at name resolution: per-instance styling on that path goes through CSS classes, as described under Styling exported icons. Wanting the parameterized forms above is the signal to switch to the module rather than reimplement them one at a time.
Styling exported icons
Exported files carry only xmlns, viewBox, width, and height on the root element, plus whatever presentation attributes the icon set bakes into the inner markup. Most sets (Tabler, Lucide, Material Design Icons) draw with currentColor, so exported icons inherit the surrounding CSS color with no extra work:
.nav a { color: #667; }.nav a:hover { color: #06c; } /* icons follow automatically */Sizing, rotation, and flipping are equally CSS-friendly (width, height, transform: rotate(90deg), transform: scaleX(-1)) when applied per-instance through a class. If you prefer setting attributes at render time instead, that is exactly what the hugo-swarm-icons module's size, fill, stroke, rotate, flip, and class parameters do.
Pre-generate a sprite sheet
When the same icons repeat across headers, menus, and footers, inline copies add up. Generate a sprite sheet once, before Hugo runs, and reference symbols with <use>.
Create tools/gen-sprite/main.go in the site repository:
package mainimport ( "bytes" "fmt" "os" swarmicons "github.com/frostybee/go-swarm-icons")func main() { p, err := swarmicons.NewJsonCollectionProvider("resources/json/tabler.json") if err != nil { panic(err) } manager := swarmicons.Default("tabler", p) names := []string{"home", "star", "search"} sc := swarmicons.NewSpriteCollector() refs := &bytes.Buffer{} for _, name := range names { icon, err := manager.Get(name) if err != nil { panic(err) } _, _, w, h := icon.ViewBox() sc.Register(fmt.Sprintf("i-tabler-%s", name), icon.Content(), fmt.Sprintf("0 0 %d %d", w, h)) fmt.Fprintf(refs, `<svg><use href="#i-tabler-%s"></use></svg>`, name) } sheet := sc.SpriteSheet(refs.Bytes()) if err := os.WriteFile("layouts/partials/icon-sprite.html", sheet, 0o644); err != nil { panic(err) }}Wire it into the build:
go run ./tools/gen-sprite && hugoInclude the generated partial once per page, near the end of layouts/_default/baseof.html:
{{ partial "icon-sprite.html" . }}Templates then reference symbols instead of inlining full markup:
<svg class="icon" width="24" height="24"><use href="#i-tabler-home"></use></svg>Sprite Sheets covers SpriteCollector in detail, including ID namespacing and multi-page builds.
For theme authors
- Ship the icons with the theme: exported SVGs under the theme's
assets/icons/directory work through Hugo's normal asset resolution, so sites using the theme need no CLI setup of their own. - Publish a name index:
swarm-icons manifest generate --path assets/icons/tabler --output data/icons.jsonwrites a JSON list of available icon names into Hugo'sdata/directory, handy for building an icon gallery page or validating names in templates. - Document the update path: a line in the theme README telling users to run
swarm-icons json updateplus the export command keeps vendored icons current.
See also
- Working with Icon Sets: the full browse, download, and update workflow behind the export step
- Icon Commands: every
icon export,icon list, andicon searchflag - Sprite Sheets: how
SpriteCollectordeduplicates repeated icons - Goldmark Integration: the
:icon[prefix:name]syntax for Go-native Markdown pipelines