Hugo Theme (Part 2) : Essentials

Intro

In the last post we created a skeleton for our Hugo theme based on Tailwind CSS. In this post, we are going to add some essential components.

We will create a new menu in our header section.

In layouts/partials/header.html

Store the current page in a variable, we will need it later

{{ $currentPage := . }}

Add two divs, first one for showing drop down buttons on mobile and the second one for the menus.

<nav class="sticky top-0 pt-4 sm:pt-0 z-10 bg-inherit border-b">
    <div class="sm:hidden w-full flex justify-between">
    </div>
    <div id="menu" class="hidden sm:flex w-full mx-auto justify-between items-center py-4">
    </div>
</nav>

We will add SVG icons as buttons to the first div and add the code to handle the dropdown in our javascript file

In the menu div, we will loop over the site main menu

<div class="w-full sm:w-4/5">
    {{ $url := .RelPermalink }}
    {{ range .Site.Menus.main }}
    {{ $active := eq $url .URL }}
    {{/* {{ $active := $currentPage.IsMenuCurrent .Menu . }} */}}
    <a href="{{ .URL }}" class="pr-6 py-2 text-xl font-medium block sm:inline-block sticky sm:relative text-center transition ease-in-out duration-150
        {{ if $active }}!underline !underline-offset-8{{ end }}">{{ upper .Name }}</a>
    {{ end }}
</div>

We will also add SVG buttons to toggle light and dark mode and add the code to handle that in our javascript as well

<div class="w-full sm:w-1/5 flex justify-center sm:justify-end">
    <svg id="day-switch">
    </svg>
    <svg id="night-switch">
    </svg>
</div>

Let’s add the javascript now.

On load, we check whether the reader has already set the dark theme. If not, we can also check whether the browser has set a preferred theme. The theme toggle switches just add or remove the dark class on the html node.

Open and close menu buttons just toggle the visibility of the buttons and menus.

In assets/js/main.js

if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark')
} else {
    document.documentElement.classList.remove('dark')
}

document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('day-switch').addEventListener('click', () => {
        localStorage.theme = 'light';
        document.documentElement.classList.remove('dark');
    });

    document.getElementById('night-switch').addEventListener('click', () => {
        localStorage.theme = 'dark';
        document.documentElement.classList.add('dark');
    });

    document.getElementById('open-menu').addEventListener('click', () => {
        document.getElementById('menu').classList.remove('hidden');
        document.getElementById('open-menu').classList.add('hidden');
        document.getElementById('close-menu').classList.remove('hidden');
    });

    document.getElementById('close-menu').addEventListener('click', () => {
        document.getElementById('menu').classList.add('hidden');
        document.getElementById('close-menu').classList.add('hidden');
        document.getElementById('open-menu').classList.remove('hidden');
    });
});

Metadata

For metadata of a post on homepage and on post page, we will show the date, categories, series and tags (if any). We will create partial templates for them and use them in the metadata.

In layouts/partials/metadata.html

<div class="metadata">
    {{ partial "date.html" .}}
    
    {{ with .Page.Params.Categories }}
    {{ partial "taxonomy/categories.html" . }}
    {{ end }}
    
    <div class="pt-2">
    {{ with .Page.Params.Series }}
    {{ partial "taxonomy/series.html" . }}
    {{ end }}
    </div>
    
    <div class="pt-2">
    {{ with .Page.Params.Tags }}
    {{ partial "taxonomy/tags.html" . }}
    {{ end }}
    </div>
</div>

In the partial template for date, add a calendar SVG and put the date time in the format Mon, Jan 2, 2006

In layouts/partials/date.html

<div class="flex items-center">
    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-due inline -ml-[3px]" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none" />
        <path d="M4 5m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" />
        <path d="M16 3v4" />
        <path d="M8 3v4" />
        <path d="M4 11h16" />
        <path d="M12 16m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
    </svg>
    <time class="" datetime="{{ time.Format "2006-01-02T15:04:05-07:00" .Date }}">{{ time.Format "Mon, Jan 2, 2006" .Date }}</time>
</div>

The categories, series and tags have some shared HTML, so we will extract that in a separate template layouts/partials/taxonomy/template.html. Let’s create a partial template for series and pass in the name and class name

In layouts/partials/taxonomy/series.html

{{ partial "taxonomy/template.html" (dict "items" . "linkClass" "series" "linkBase" "series") }}

In layouts/partials/taxonomy/template.html, we can use the passed information to the similar HTML with different content and different classes applied.

{{- $linkClass := .linkClass -}}
{{- $linkBase := .linkBase -}}

<div class="flex flex-row flex-wrap">
  {{- range $index, $el := sort .items -}}
    <!-- Replace certain special characters with their URL encoded counterparts -->
    {{- $item := replace . "#" "%23" -}}
    {{- $item = replace $item "." "%2e" -}}
    {{- $link := ( printf "%s/%s/" $linkBase ( $item | urlize ) ) | relLangURL -}}
    <a class="{{ $linkClass }} mr-3 text-gray-800 dark:text-slate-200 rounded-xl bg-slate-200 dark:bg-slate-500 px-2 leading-6 text-base" href="{{ $link }}">{{- . -}}</a>
  {{- end -}}
</div>

Let’s add # to the tag names using CSS.

In assets/css/main.css

.tag::before {
    content: '#'
}

Pagination

We will add pagination to list all the posts at /posts URL. We will use Hugo’s built in pagination and style it a bit.

In layouts/_default/list.html, we can group the pages according to year and pass them to the paginator

{{ define "main" }}
{{ $paginator := .Paginate (.RegularPagesRecursive.GroupByDate "2006") 10 }}

    {{ range $paginator.PageGroups }}
    {{/* Some HTML is rendered here */}}
    {{ end }}
    
		<div class="mt-auto pt-10">	
    {{ template "_internal/pagination.html" . }}	
    </div>
{{ end }}

In assets/css/main.css, we can just add some styles to the pagination, like backgrounds, borders, dark mode colors, etc.

.pagination {
    @apply list-none m-0 p-0;
    @apply flex flex-wrap justify-center items-center;
}

.page-item {
    @apply m-2 px-4 py-2;
    @apply border border-neutral-700 dark:border-neutral-300;
}

.page-item:first-child {
    @apply rounded-l-lg;
}

.page-item:last-child {
    @apply rounded-r-lg;
}

.page-item a {
    @apply no-underline;
}

.page-item.disabled a {
    @apply text-neutral-300;
}

.page-item.active a {
    @apply text-neutral-800 dark:text-white;
}

Thank you for reading. Check out the other parts in the series below.