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.
Link your new theme in configuration
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.