Hugo Theme Tutorial – Very Brief

A ‘theme’ in Hugo is a collection of HTML templates, stylesheets and other components that define what your site looks like, and are completely independent of your site content. That is, the very same theme can be used in many different websites. In software lingo this is known as the ‘separation of concerns’ principle, which reminds of an old Swedish joke claiming that policemen always patrol in pairs because one can read and another can write.

Jokes aside, though, you can have several themes under themes sub-directory and switch between them by changing a single parameter in your project configuration; or, if you so choose, you might share the theme you’ve created with the world. As you can imagine, there is also a lot of Hugo themes available already, but it is still worth to explore a bit, how a Hugo theme is organized.

So, let’s create a theme (I’ll call it ‘comic’ – it’s just a name):

hugo new theme comic

Hugo will happily report:

Creating theme at /PATH/TO/HOME/multilingua/themes/comic

Running tree command reveals the new sub-tree under themes/comic. It’s quite similar to the structure of our main project, but content/ directory is understandably missing:

$ tree
.
├── archetypes
│   └── default.md
├── config
│   └── _default
│       ├── config.yaml
│       └── languages.yaml
├── data
├── kontent
├── layouts
├── resources
│   └── _gen
│       ├── assets
│       └── images
├── static
└── themes
    └── comic
        ├── LICENSE
        ├── archetypes
        │   └── default.md
        ├── layouts
        │   ├── 404.html
        │   ├── _default
        │   │   ├── baseof.html
        │   │   ├── list.html
        │   │   └── single.html
        │   ├── index.html
        │   └── partials
        │       ├── footer.html
        │       ├── head.html
        │       └── header.html
        ├── static
        │   ├── css
        │   └── js
        └── theme.toml

The crucially important HTML template files are those under _default sub-folder and index.html. To generate your site with Hugo, these should be sensibly configured. It doesn’t happen by default (all of them except baseof.html are just empty). So, let’s get this done.

The mother of all templates: baseof.html

Open the themes/comic/layouts/_default/baseof.html file and replace its contents with the following:

<!DOCTYPE html>
<html lang="{{ .Language.Lang }}">
    <head>
        <title>
            {{.Title}}
        </title>
        <meta name="description" content="{{ .Description }}">
        <link rel="stylesheet" href="/css/main.css">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta charset="utf-8">
    </head>
    <body>
        <main>
            {{block "main" .}}
            {{end}}
        </main>
    </body>
</html>

It’s the very top and the very bottom of any web page of your site. All pages of your website will be rendered using this template by default. The code in double curly braces will be replaced with the parts of the actual content. {{.Title}} will become the title of an actual page. The {{block "main" .}}...{{end}} part will include template code from a different template file, depending on the type of the page – more on that in a moment. We shall discuss {{.Language.Lang}} and {{.Description}} later, although you likely understand already what they do.

Defining the singularity: single.html

The single.html file defines how a page with a single unit of content (an article, a cooking recipe, etc.) is generated. Open the themes/comic/layouts/_default/single.html and put the following text into it:

{{define "main"}}
<article>
    {{.Content}}
</article>
<p class="debug">
    Page generated with <code>single.html</code>
</p>
{{end}}

The code between {{define "main"}} and {{end}} corresponds to the code block between {{block "main" .}} and {{end}} in baseof.html. Whenever a single page has to be generated, Hugo will look into the single.html file, look up a block named “main” in our example, render it (that is, replace variables with actual content and other data for that particular page), and nest it into baseof.html.

To make it easier to understand which templates are used for what purpose, an explanatory text is added at the bottom. Remove it later on.

Showing what’s in the store: list.html

Your site should be able to list its content for the users, not just displaying it, and provide navigation. For displaying multiple units of content – for instance, to list the links to all the articles in a section – a separate template is used: list.html. Open themes/comic/layouts/_default/list.html and put this text into it:

{{define "main"}}
{{.Content}}
<ul>
{{range .Pages}}
    <li>
        <a href="{{.Permalink}}">
            {{.LinkTitle}}
        </a>
    </li>
{{end}}
</ul>
<p class="debug">
    Page generated with <code>list.html</code>
</p>
{{end}}

Iterating through a list of ‘child’ pages ({{range .Pages}} block) creates a list item for each page in turn with a link to a child page ({{.Permalink}}) and the (link) title of the page as the link text. Page titles and link titles are discussed in more details here . As with a single page, the result is nested into baseof.html.

A list page can have a content of its own (for instance, a description of a section, category, etc.). The {{.Content}} variable at the top of the block displays such content.

Notice that it doesn’t matter, what exactly is being listed: it can be ‘single’ pages, or sub-sections, sub-categories, sub-anything.

The entry point: index.html

That’s the starting point of your website, and thus a separate template. Open themes/comic/layouts/index.html (empty by default) and put this into it:

{{define "main"}}
<ul>
{{range .Site.Sections}}
    <li>
        <a href="{{.Permalink}}">
            {{.LinkTitle}}
        </a>
    </li>
{{end}}
</ul>
<p>
    Page generated with <code>index.html</code>
</p>
{{end}}

This doesn’t differ much from list.html template, but for the sake of diversity, we are iterating over a global variable, .Site.Sections, containing the top sections of your site. Needless to say, it is a rather unassuming front page for a website, and should be re-designed later. For now, though, we just need to get things working.

Open config/_default/config.yaml and tell Hugo you are going to use your new theme:

baseURL: "http://example.com/"
title: "Multilingua"
contentDir: "kontent"
theme: "comic"

If you now check the http://localhost:1313 , you will see a page saying ‘Page generated with index.html’ (reload may be required). Once you create some content, the page will display more than this debugging message, of course.

Done with Basics

The very basic Hugo theme is now ready: your pages will be displayed.

Should Hugo generate usable templates immediately when a new theme skeleton is created, this article to this point would become obsolete.

Partial Templates, or Just Partials

Partials allow you to define chunks of HTML code and include it, conditionally or unconditionally, into other templates. A typical candidate for a partial is a code fragment that includes a lot of template code and a relatively small amount of HTML.

A partial can be called recursively, that is, be included into itself – just make sure it won’t recurse endlessly. This allows us to create a partial that would produce the so-called breadcrumbs for easier site navigation:

{{- with .Parent -}}
{{partial "breadcrumbs.html" . -}}
{{- if .Parent -}}
<span>:</span>
{{- end -}}
<a href="{{.Permalink}}">{{.LinkTitle}}</a>
{{- end -}}

In case you are not familiar with the jargon: breadcrumbs is a sequence of links starting (from the right) with the parent page, then the parent of the parent, and so on up to the home page. Dropping breadcrumbs to mark a path in a labyrinth is probably where the terms originates from.

Save this partial under layouts/partials/breadcrumbs.html sub-directory of your theme. It can be included into another template like this:

{{partial "breadcrumbs.html" .}}

Mind the dot! The ‘dot’ passes the context to the partial. Without it, no variables will be available there.

Since we want breadcrumbs on every page of our site, let’s include it into baseof.html template:

<!DOCTYPE html>
<html>
    <head>
        <title>
            {{.Title}}
        </title>
        <link rel="stylesheet" href="/css/main.css">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta charset="utf-8">
    </head>
    <body>
        {{partial "breadcrumbs.html" .}}
        <div class="container">
            <main>
                {{block "main" .}}
                {{end}}
            </main>
        </div>
    </body>
</html>

Static Assets

Finally, the static files live under static directory in your theme. You probably want to add some basic CSS right away, just enough to prevent your eyes from bleeding while prototyping your site. For instance, you may save this unassuming grid-based stylesheet under static/css/main.css in your theme:

@import url('https://fonts.googleapis.com/css2?family=Open+Sans+Condensed:wght@300;700&family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600&display=swap');

body {
    font-family: "Open Sans", sans-serif;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
    font-family: Open Sans Condensed, sans-serif;
}

h1.languages {
    font-family: "Open Sans", sans-serif;
    font-size: 1rem;
    font-weight: 400;
    margin-bottom: 0;
}

p {
    line-height: 1.7;
}

p.debug {
    color: #999;
    font-size: 0.7rem;
}

.container{
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: 2rem;
    row-gap: 1rem;
}

main {
    grid-column: 4/10;
    grid-row: 1;
    width: 100%;
}

.translations {
    grid-column: 1/3;
    grid-row: 1;
    padding-left: 1rem;
}

.translations a {
    display: block;
}

Should I Use a Third-Party Theme, or Build My Own?

You probably should play around a bit with a vanilla Hugo project and your own theme to understand how things work. Then, you just need to decide what makes more fun for you – fiddling with CSS and Go templates wrapped in Hugo, or just to start blogging. I cannot resist endorsing my own theme called Errorist, but in all fairness, there are hundreds more available.

The Further Steps

At this point it would make sense to deal with the content and the structure of your website. Once you’ve got a clear idea about that, you should probably re-design your from page template to put your best foot forward, style your website properly, maybe add scripts under static/js, and create a 404.html page, which will be handy if you host your site on GitHub.

If you are at home with Git, consider making your theme a separate Git project and include it in your main site repository as a sub-module.

Built with Errorist theme for Hugo site generator.