Brian Wagner | Blog

Hugo Theme Creation: Step-by-Step

Dec 30, 2020 | Last edit: Dec 31, 2020

Hugo themes are easy to find and easy to implement on a site. But what if we want to build our own? This would be ideal if you're bringing your own templates or styles from an existing project. Or maybe you have a clear design in mind already.

While so much of Hugo's documentation is really great, I have struggled to find a simple guide for creating a theme from scratch. So let's explore the process, step-by-step.

The basics involve:

  1. use the "hugo new ..." command to generate the boilerplate files for a new theme
  2. set the site config to use the new theme
  3. modify global page structure
  4. apply custom layout for the homepage
  5. create the markup for the default content collection page ("list" page)
  6. provide markup for the individual content page ("single" page)
  7. optional: create a "list" and "single" page for other content types
  8. create a navigation component
  9. attach stylesheet

This is a sequence I've found useful to integrate with Hugo's somewhat idiosyncratic content management system.

Note: this does NOT focus on building accessible HTML content or awesome user experiences. There's a lot of resources out there for that.

1. Create a New Theme

When you create a new site with the Hugo CLI, one of the helpful messages you get is about adding a theme.

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/ or
   create your own with the "hugo new theme <THEMENAME>" command.

This will create a new directory in the site's themes folder, and generate most of the files we need to get started. One file we can edit immediately is theme.toml, in the root of the new theme directory. This file is where the theme name and author are defined. Add your name and site URL, if desired. Let's also remove the "[original]" property, as this is not a sub-theme of another theme.

2. Assign the Site Theme

Now we have a basic Hugo theme, but the current site doesn't know anything about it. So open the site's config.toml file and add the theme name for the "theme" value here.

3. Modify Global Page Structure

The boilerplate for a new theme includes some html templates. The first one to explore is layouts/_default/baseof.html. The structure of this file should be a clear giveaway that it's a root page template, as it has "doctype" declared as well as the main html tag. It also pulls in template partials for the page head, header and footer -- each of these are found in the partials/ directory.

More importantly this file defines the "main" block. This syntax may be familiar for those with experience in Go html templates, or Twig templating. A block is like a placeholder that will be replaced by a corresponding block in a child template. Multiple blocks can be used in a root template like this, so take care to match the names. Otherwise, the template render process will not be able to perform the block replacement, when needed.

At this point, feel free to add some links to scripts or stylesheets in the head, create additional page sections, or modify the basic site structure. Changes to layouts/_default/baseof.html will apply to all pages on the site.

4. Add Markup to the Homepage

We're making progress, but chances are we'll still see a blank page if we try to run "hugo server" and test our site. Remember the "main" block from above? Well we need to define that in our child templates as well -- something the CLI command could have done for us! A pet peeve of mind, but easy to resolve.

For starters, open the layouts/index.html file which is the default homepage. Add the "main" block here, along with content -- demo or real -- and we have a basic homepage.

{{ define "main" }}
  <h1>Page title</h1>
{{ end }}

5. Add Markup to the Content List Page

Similar to the index page above, we want to add the "main" block to other child pages. The first to consider is the default collection, or "list" page. That's found at layouts/_default/list.html.

Inside the block, we can add custom content, or use some Hugo variables to generate markup from our site content. The most basic pattern is to add a feed of links or teasers to the content pages.

{{ define "main" }}
  {{ range .Site.RegularPages }}
    {{ .Title }} - {{ .Permalink }}
  {{ end }}
{{ end }}

Hugo List Pages

6. Add Markup to the Content Single Page

Once more, let's add the "main" block to the page for an individual piece of content. That's in the layouts/_default/single.html. Here we can access the various properties for each content item, such as the frontmatter as well as the raw content section.

{{ define "main" }}
  <h1>{{ .Title }}</h1>
  <p>{{ .Date }}</p>
  {{ .Content }}
{{ end }}

Note: Hugo will wrap the Content field in a p tag by default, so we don't need to add that html tag here.

Hugo Single Pages

7. Optional: Add Pages for Other Content Types

At this point, all of our content will flow through the two templates we defined above in the _default directory. If we have different "archetypes" -- or types of content -- we can create a different set of templates for those. Imagine we have a site with two archetypes: articles and authors, where each has a few custom fields in the frontmatter. The default template works well for the articles type, but we want the authors pages to look different.

Within the layouts/ directory, create another directory with the same name as the archetype. Create a "list" template, a "single" template, or both. Now add the "main" block in each, and apply the custom html required.

8. Navigation

One thing the boilerplate is missing is a default navigation component. We can create one directly in an existing partial -- header.html, for example -- or create a new partial for that.

Hugo expects the menu links to be defined in the site's main config.toml file. Once they are defined there, we can access them in the template:

{{ range .Site.Menus.main }}
  <a href="{{ .URL }}" title="{{ .Title}}">{{ .Name }}</a>
{{ end }}

Hugo Menu Templates

9. Attach Stylesheet

Hugo has a processing pipeline called Pipes that is enabled by default in our new theme. To use it properly we need to create an assets/ directory inside our theme folder -- something the CLI doesn't do for us -- and place relevant files in there.

For simple SASS processing, create a directory called "sass" inside the assets/ directory. Add a main.scss file, for example. And call the Pipe in the partials/head.html, like this:

<head>
  {{ $sass := resources.Get "sass/main.scss" }}
  {{ $style := $sass | resources.ToCSS }}
  <link href="{{$style.Permalink}}" rel="stylesheet" />
</head>

Hugo SASS / SCSS

10. Going Further

This is a basic theme to get started, but it's just skimming the surface of what's possible with Hugo. At this point, I suggest consulting the official docs to see how Hugo handles some common site-building patterns, like formatter functions and template overrides.

Content View Templates

The "show" template is the most basic layout for a piece of content. Any number of layouts can be created in the _default/ directory or for a specific archetype. in Hugo terms, these are "content view" templates. It can be especially useful to create a layout for a teaser or summary view.

These customer views can be applied over a range of content, for example, with the Render function.

{{ range .Pages }}
  {{ .Render "summary"}}
{{ end }}

It's also possible to specify the layout for an individual piece of content by setting the "layout" property in the frontmatter. This can be useful to override the "single" layout for a specific page.

Date formats

Creating Related Content sections