How to Create Custom Hugo Themes

Siddhant Varma
16 min read
platforms
TL;DR: Create custom Hugo themes to match your brand by building templates, layouts, and styling from scratch.
  1. Theme Structure: Use layouts for HTML templates, static folder for CSS/JS assets, and partials for reusable components
  2. Multi-Author Support: Store author data in JSON files and reference them in post metadata for dynamic rendering
  3. Key Features: Built-in pagination, taxonomy system for categories/tags, and automatic SEO optimization with meta tags
This tutorial walks through building a multi-author blog theme with complete styling, navigation, and search engine optimization.

Lately, developers are adopting static site generators like Hugo to quickly deliver content to their audience. Frameworks like these cut down on issues that pop up with scalability, version control, managing dependencies, and performance.

Hugo in particular offers tons of features right out of the box for creating fast, modern, and secure static websites. It’s written in Go, a powerful server-side scripting language, and offers a clean workflow for creating landing pages, portfolios, documentation, or blogs without a lot of extra layers of complexity.

Hugo also has plenty of themes ready to go right away, but a prebuilt theme probably isn’t going to match your brand’s vision. Most likely, you’re going to want a custom theme, something that affords you fine-grained control over things like color schemes, typography, and UI components.

In this article, I’ll walk you through how to quickly customize your Hugo site for your desired brand and aesthetic by building your own theme.

Table of Contents

How to Create a Multi-author Hugo Blog Theme

Specifically, I’ll demonstrate how to create a multi-author blog theme in Hugo. I’ll cover paginating multiple blog posts, adding tags or categories, setting up support for multiple authors, and adding some SEO considerations to give your site more presence on the web.

Spinning Up a Site

Once you have Hugo installed (refer to the complete installation guide here) and a modern code editor (like VS Code), you’re ready to begin.

In a directory of your choice, create a new Hugo site by running the following command:

{% raw %}

hugo new site multiauthor-blog-site

Once a new Hugo project is created for you, navigate into the directory.

cd multiauthor-blog-site

Open the project in your editor and run the following command to spin a development server where you can see your changes live:

hugo server

Creating Your First Hugo Theme

With your brand-new site up and running, it’s time to start customizing the canvas.

Create a new theme inside the root directory by running hugo new theme blog-theme. This generates a folder called blog-themes inside the themes directory containing a bunch of files and folders. You’ll mostly be working with two directories: /blog-theme/layout, where all your HTML files will be present, and /blog-theme/static for static assets like images, CSS styles, and custom JavaScript.

It’s time to tell Hugo that you’ll be using this new theme for your site. Usually, those details go inside a configurational file config.toml in the root directory. Add a key theme with a value of your theme’s name (ie, blog-theme) as shown:

baseURL = "http://example.org/"
languageCode = "en-us"
title = "My New Hugo Site"
theme = "blog-theme"

Inside the baseof.html file (theme/blog-theme/layouts/default/baseof.html), add the following markup:

<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
    {{- partial "head.html" . -}}
    <body>
        {{- partial "header.html" . -}}
        <main>
        {{- block "main" . }}{{- end }}
        </main>
        {{- partial "footer.html" . -}}
    </body>
</html>

The previous code creates some semantic elements for your site and references a site variable from your config.toml file in Hugo’s default templating language Go. If you look inside your /theme/blog-theme/layouts/partials folder, you’ll find a head.html file containing all the markup for your site’s <head> tag. Similarly, you can find a partial footer.html file to render a footer on your site.

How to build a content engine.

The rest of the content of your site should go inside the <main> tags.

Add the following lines inside /theme/blog-theme/layouts/index.html to tell Hugo to insert the page content between the <main> tags in the baseof.html file:

{{ define "main" }}
{{ .Content }}
{{ end }}

Creating and Styling the Navigation Bar

The navigation bar allows users to easily visit different sections of your site.

Inside /theme/blog-theme/layouts/partials, you can find a header.html that acts as the default header for your site. Put all the HTML for the navbar inside this file. Hugo will automatically render it on your site.

<header>
    <nav class="navbar" role="navigation">
        <div class="navbar__left">
            <a href="/"><strong>blogsite.com</strong></a>
        </div>
        <div class="navbar__right">
            <a href="/blog">Blogs</a>
            <a href="/categories">Categories</a>
        </div>
    </nav>
</header>

The / link is to direct the user to your site’s homepage. You can leave the remaining links as it is for the moment.

Your navbar could look better, so let’s walk through how to add custom CSS styling to your theme. The static folder inside your theme holds all the static content related to your theme, including custom styles for your templates. So all stylesheets pertaining to your theme go inside the /theme/blog-theme/static/css folder.

Create a file called style.css and reference it inside your theme’s head.html file (/blog-theme/layouts/partials/head.html) as shown:

<link rel="stylesheet" href="/css/style.css" type="text/css" media="all" />

Hugo will push all your styles from this file onto your HTML pages.

You can create multiple stylesheets to break down your styles into logical modules. For brevity’s sake, let’s keep all the styles for this project inside style.css only.

body {
    background-color: #fcfcfc;
    color: #333;
    font-size: 125%;
    line-height: 1.5;
    margin: 0 auto;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  }
  .content {
    margin-bottom: 2rem;
  }

 .navbar{
   display: flex;
   align-items: center;
   justify-content: space-between;
   height: 25px;
   background: gainsboro;
   padding: 10px;
 }
 .navbar__right{
   display: flex;
 }
  a{
    text-decoration: none !important;
    color: black;
  }
 .navbar__right a{
   text-decoration: none;
   font-size: 14px;
   margin-right: 10px;
   color: black;
   transition: all 100ms;
 }
 .navbar__right a:hover{
   text-decoration: underline;
   font-weight: bold;
 }
 main{
   margin: 0 200px;
 }

You can easily style other pages following the same pattern.

Adding, Organizing, and Listing Blog Posts

All the markdown files for your site go inside the content folder in the root directory of your project. Hugo renders the main content for your homepage from an _index.md file inside the content directory.

Create an _index.md as shown:

# Welcome to My Multi-Author Blog
Hola, devs! Welcome to my site.

If you visit http://localhost:1313, you can see a simple homepage for your site.

Screenshot of the homepage

Inside the content directory, create a blogs folder where you can organize your blog posts. You can segregate them further based on the year of publication. The following command creates a new blog post in the designated directory with some metadata:

hugo new /blog/2020/fundamentals-design-principles.md

Inside that file, add some dummy content:

---
title: "Fundamentals of Design Principles"
date: 2020-12-27T22:37:51+05:30
draft: true
---
![](https://images.unsplash.com/photo-1516638022313-53fa45a84c7f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80)
### Diving deeper into design principles and methodologies
Lorem...

While you’re at it, add a few more blogs with some content using the same process:

hugo new /blog/2020/java-spring-mvc.md
hugo new /blog/2020/hugo-themes.md

Hugo renders each individual blog post on the URL /blog/2020/java-spring-mvc by default. If you visit http://localhost:1313/blog/2020/java-spring-mvc, you can see the specific blog post.

Let’s create a page for your theme that lists all the blogs and allows a user to navigate to a particular post.

Recall that the navbar contains a route directing to /blogs, so you can render a list of all the blogs when the user visits /blogs. Hugo has a default directory where it looks for list type pages; this is where you can create a generic template for any kind of lists you want to render.

Inside /blog-theme/_default/list.html, add the following code, which simply iterates over the .Pages variable that contains a list of all child pages for /blogs. Inside the range loop, the current context (or the dot.) is always an individual page that’s used to generate the customized link.

{{ define "main" }}
<div class="container">
    <div class="section">
        <div class="content">
            <h1 class="title">{{ .Title }}</h1>
            {{ .Content }}
            {{ range .Pages }}
            <ul>
                <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
            </ul>
            {{ end }}
        </div>
    </div>
</div>
{{ end }}

Now, if you now go to http://localhost:1313/blog, you will see a list of all your blogs. Navigate to each blog by clicking an item on the list.

Creating a Blog Post Template

Next, let’s create a template for your theme that renders each new blog post exactly as you’d like it to appear.

Head over to the layouts folder (/theme/blog-theme/layouts) and create a folder named blogs with a file single.html inside it. This structure allows Hugo to render blogs faster, as it’s higher in the lookup order with respect to the single.html file present in the _default (/theme/blog-theme/layouts) directory.

{{ define "main" }}
<section class="section">
  <article>
    <div class="blog__container">
          <h1 class="blog__title">{{ .Title }}</h1>
            <div class="blog__details">
              <div class="author-image-container">
                  <img src="" alt="author-image" />
              </div>
              <div class="blog__info">
                <p>By  author name </p>
                <p><time>{{ .PublishDate.Format "January 2, 2006" }}</time> |
                    {{ .ReadingTime }} {{ if eq .ReadingTime 1 }} minute {{ else }}                              minutes {{ end }} read
                 </p>
              </div>
              <div class="blog__categories">
                Blog categories
              </div>
            </div>
          <div class="content">
            {{ .Content }}
          </div>
        </div>
  </article>
</section>
{{ end }}

The previous markup structures the contents of the blog post page by first rendering the blog’s title, followed by metadata like author name, image, an average read time, and related content categories.

Let’s style this markup:

.author-image{
  object-fit: cover;
  border-radius: 50%;
  width: 48px;
  height: 48px;
}
.blog__title{
  font-size: 32px !important;
  font-weight: bolder;
}
.blog__details{
  display: flex;
  align-items: center;
}
.blog__info{
  margin-left: 20px;
  flex: 1;
}
.blog__info p{
  margin-block-start: 2px;
  margin-block-end: 2px;
  font-size: 14px;
}
.blog__image > img{
  height: auto;
  width: 100%;
  object-fit: contain;
  margin: 10px 0;
}
.blog__categories{
  display: flex;
  align-items: center;
}
.category{
  padding: 5px 5px;
  background: #f07979;
  font-size: 12px;
  border-radius: 5px;
  width: auto;
  margin-right: 5px;
}
.category a{
  color: #fff;
}
.content img{
  height: auto;
  width: 100%;
  object-fit: contain;
  margin: 10px 0;
}

Go to /blogs and click on a blog post to see some structured content of that post. Since other details, like author information, haven’t been hooked yet, you’ll only see some boilerplate text.

Add some hard-coded categories for your blogs inside the config.toml file:

[params]
    categories = ["Web Development","Blogging","Web Design"]

You can now simply loop over these categories and render them inside the single.html.

Adding Support for Multiple Authors

Now your content is there, but there’s still no information about the author.

If you were building a single author template, you could create a variable for author name and image in the config.toml file and reference it inside the single.html file as you did for categories in the previous section. Since you want more than one author on your site, you need to add support for multiple authors in your theme.

Inside the root folder, head over to the data directory and create a JSON file with the name of the author (for example, siddhantvarma.json) with the following information in JSON format about the author:

{
    "name": "Siddhant Varma",
    "bio": "Howdy Guys! I'm a React and JavaScript Developer from India. I love watching             movies and creating content for developers",
    "avatar": "/img/authors/siddhantvarma.jpeg",
    "social": {
        "linkedin":"https://www.linkedin.com/in/siddhantvarma99/"
    }
}

Add more author information to your preference, like social media handles and external sites. You can use the following structure to organize your authors:

├── data
    └──authors
        └── siddhantvarma.json
        └── sammills.json
        └── randylev.json

Create as many authors as you like. Add an author key and value pair inside the metadata for each post as shown to associate an author with a particular blog post/ Yyou must ensure that the author’s name matches exactly the name of the JSON file for that author.

---
title: "Java Spring MVC"
date: 2020-12-27T20:08:49+05:30
author: randylev
---

Now each blog post is associated with a JSON file describing some details about that post’s author.

You can go back to single.html and render the author properties on the page along with the categories added in the previous section.

{{ define "main" }}
<section class="section">
  <article>
    <div class="blog__container">
      {{ $author := index .Site.Data.authors (.Params.author | default "default") }}
          <h1 class="blog__title">{{ .Title }}</h1>
            <div class="blog__details">
              <div class="author-image-container">
                <img src="{{ $author.avatar }}" class="author-image">
              </div>
              <div class="blog__info">
                <p>By
                  {{- if $author -}}
                      {{ $author.name }}
                  {{- end -}}
                </p>
                <p><time>{{ .PublishDate.Format "January 2, 2006" }}</time> |
                    {{ .ReadingTime }} {{ if eq .ReadingTime 1 }} minute {{ else }} minutes {{ end }} read</p>
              </div>
              <div class="blog__categories">
                {{ range $idx, $category := .Site.Params.categories }}
                  <div class="category"><a href="{{ "categories/" | relURL }}{{ $category | urlize }}">{{ $category }}</a></div>
                {{- end }}
              </div>
            </div>
          <div class="content">
            {{ .Content }}
          </div>
        </div>
  </article>
</section>
{{ end }}

And there’s your blog with some content, metadata, an author image, and categories. You can easily navigate to other blog posts and see how various author information is rendered.

Your blog post template

Implementing Pagination

Ensure good user experience on your site by allowing a user to easily paginate through the blog posts. Hugo offers this feature right out of the box by providing the variables .PrevInSection and .NextInSection. These link to the previous and next page objects to implement simple pagination for your theme.

The following code renders two buttons at the end of each post and directs the user to the immediate previous and next post with respect to the current post.

    <div class="blog__navigate">
      <div class="blog__navigateButtons">
        <div class="blog__navigateButtons__previous">
          {{ with .PrevInSection }}
          <button><a href="{{ .Permalink }}">&#8592; Previous Post: {{ .Title }}</a>               </button>
          {{ end }}
        </div>
        <div class="blog__navigateButtons__next">
          {{ with .NextInSection }}
          <button><a href="{{ .Permalink }}">Next Post: {{ .Title }} &#8594;</a></button>
        {{ end }}
        </div>
      </div>
  </div>

Let’s style this layout inside style.css:

.blog__navigateButtons{
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.blog__navigate{
  margin-bottom: 20px;
}
.blog__navigate button{
  border: none;
  outline: none;
  background: rgb(88, 123, 240);
  color: #fff;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
  transition: all 200ms;
}
.blog__navigate button:hover{
  transform: translateY(1.5);
  background: rgb(43, 92, 226);
}
.blog__navigate a{
  color: #fff;
}

Now you have a set of fully functional pagination buttons at the end of each post.

Pagination buttons for a Hugo theme

Creating a Category Page with Taxonomies

Categories are common on blogs for easy discovery of similar posts on your site. You already have some predefined categories inside config.toml file, so let’s create a separate category page that lists out all the categories and all the blogs associated with each one.

You need to associate blogs with your categories, so head to your markdown files and add some categories inside the metadata.

---
title: "How to create multi author blog site using Hugo"
date: 2019-03-20T18:42:38+02:00
description: "In this tutorial you're going to learn how to create a multi author blogging site in Hugo"
author: siddhantvarma
categories : ["Web Development","Web Design","Blogging"]
---

Hugo provides taxonomy, which classifies logical relationships between content. According to Hugo’s documentation, it offers default taxonomy support for categories and tags. This means you can easily set up a category taxonomy for your site.

Specify that you want to use the category taxonomy inside the config.toml file:

[taxonomies]
  category = "categories"

Inside your _default folder, create a file called taxonomy.html that ranges through the taxonomies, and then loops through each taxonomy to list out all the items pertaining to that taxonomy name. In this case, each taxonomy is a particular category, and blogs pertaining to each category are listed under that category as that taxonomy’s value.

{{ define "main" }}
<section class="section">
    <div class="container max-800px">
        <h1 class="title">
            {{ .Title }}
        </h1>
        <div class="categories">
            {{ range $taxonomyname, $taxonomy := .Site.Taxonomies }}

                 {{ range $name, $value := $taxonomy }}
              <div class="categoryCard">
                 <div class="categoryHeading"><p style="font-size:large">{{ $name }} </p></div>
                      <div class="category__detail">
                        {{ range $value.Pages }}
                            <p hugo-nav="{{ .RelPermalink}}"><a href="{{ .Permalink}}"> {{ .LinkTitle }} </a> </p>
                        {{ end }}
                      </div>
              </div>
              {{ end }}
            {{ end }}
          </div>
    </div>
</section>
{{ end }}

Apply some minimal styles to your template.

.categories{
  display: flex;
  justify-content: space-between;
}

.categoryCard{
  display: flex;
  flex-direction: column;
  width: auto;
  max-width: 200px;
  min-height: 200px;
  margin-right: 5px;
  height: auto;
  background-color: #fff;
  box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
}

.categoryHead{
  text-align: center;
  background-color: #f07979;
  color: #fff;
  font-weight: bold;
}

.category__details{
 padding: 0 10px;
}

.categoryCard p{
  font-size: 14px;
}

Visit /categories via the navbar to see your category page.
Your blog’s category page

SEO Optimization for Hugo Themes

Finally, your theme should be optimized for search engines to ensure your site draws users. Two factors in particular affect SEO for your site:

  • The SEO title
  • The meta description

Your homepage’s title can be populated from your config.toml file. For other pages, a unique title and meta description should be dynamically generated to make these pages easily discoverable by search engines.

How to turn readers into customers.

Use the following generic code inside your head.html file:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <title>{{ if not .IsHome }}{{ with .Title }}{{ . }} | {{ end }}{{ end }}{{ .Site.Title }}</title>
    <meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ with .Summary }}{{ . }}{{ else }}{{ .Site.Params.description }}{{end }}{{ end }}">
    <link rel="canonical" href="{{ .Permalink }}" />
    <link rel="stylesheet" href="/css/style.css" type="text/css" media="all" />
    {{ template "_internal/opengraph.html" . }}
    {{ template "_internal/twitter_cards.html" . }}
</head>

The previous code checks whether you’re at a root route, then fetches the title from the config.toml file. For other subroutes, it uses a different title according to the page you’re currently on and appends the homepage title to it.

If you inspect each page’s <head> tag, you’ll notice a more specific and relevant title attached to it with an SEO-friendly meta description.

{% endraw %}

Next Steps and Advanced Customization

Use the theme you built here as a boilerplate for your own multi-author blog. Customize it even further according to the needs of your brand—extend taxonomies by adding tags and maybe create promotional pages, like an about page or a contact page. Now that you also understand how custom styling works in Hugo, add some media queries to make your site more responsive.

Hugo’s functionality simplifies the potentially daunting task of creating a custom theme, while offering you fine-grained control over your branding and the flexibility to accommodate change in the future.

Frequently Asked Questions

How long does it take to create a custom Hugo theme?

Creating a basic custom Hugo theme takes 2-4 hours for developers familiar with HTML and CSS. A feature-complete theme with multi-author support, pagination, categories, and custom styling typically requires 8-12 hours. Advanced themes with custom shortcodes, complex layouts, and responsive design may take several days to develop and refine.

Do I need to know Go programming to create Hugo themes?

No, you don't need deep Go knowledge to create Hugo themes. Hugo uses Go templates for its templating language, but most theme development involves HTML, CSS, and basic template syntax like loops and conditionals. Understanding variables, range loops, and if statements is sufficient for most custom theme development.

Can I convert an existing website design to a Hugo theme?

Yes, you can convert existing HTML/CSS designs to Hugo themes by breaking down the HTML into Hugo's layout structure with partials, base templates, and single/list templates. Extract static assets to the static folder, convert navigation and headers to partials, and replace hardcoded content with Hugo variables and content files.

How do I add responsive design to my Hugo theme?

Add responsive design by including CSS media queries in your theme's stylesheet. Create breakpoints for mobile, tablet, and desktop viewports, adjust layouts with flexbox or grid, and ensure images scale properly. Test on multiple devices and use Hugo's asset pipeline for optimized, minified CSS delivery.

What's the difference between Hugo themes and WordPress themes?

Hugo themes are static site templates that generate HTML files at build time, while WordPress themes are dynamic PHP templates that generate pages on each request. Hugo themes are faster, more secure, and simpler to deploy, but lack WordPress's database-driven features and real-time content updates without rebuilding the site.

Can Hugo themes support e-commerce functionality?

Hugo themes cannot natively support e-commerce since Hugo generates static sites without server-side processing. However, you can integrate third-party services like Snipcart, Stripe Checkout, or Shopify Buy Button into your Hugo theme templates for e-commerce functionality while maintaining static site benefits.

How do I share or sell my custom Hugo theme?

Share Hugo themes by publishing them on GitHub, GitLab, or the Hugo Themes Showcase. For commercial themes, sell through marketplaces like ThemeForest or your own website. Include documentation, demo sites, and clear installation instructions. Follow Hugo's theme component standards for easier adoption.

Can I use Bootstrap or Tailwind CSS in Hugo themes?

Yes, you can use any CSS framework in Hugo themes. Include Bootstrap or Tailwind CSS via CDN in your head.html partial, or install via npm and use Hugo Pipes for asset processing. Tailwind requires additional build configuration with Hugo's PostCSS pipeline for JIT compilation and purging unused styles.

About the Author

Siddhant Varma

Siddhant is a full stack JavaScript Developer with experience in ReactJS, Firebase, Angular and NodeJS. He writes for the community to create awareness and knowledge and help the engineering community learn and grow.

Share this article:TwitterLinkedIn

Continue Reading

Explore our complete library of technical content marketing resources and developer relations insights.

View all posts

Want to learn more about how we work?