Making a hugo theme: `tule`

Continuing the discussion from Making a website: home on the web:

I realized that having tule as a complete theme would make all these other steps a lot easier on myself. We will be doing that in this thread. :slight_smile:

Here’s my thinking for a 1.0 release: the minimal of minimal yet complete set of templates to produce output for any valid content. On the lookup order doc it has this note:

Tip: The examples below looks long and complex. That is the flexibility talking. Most Hugo sites contain just a handful of templates:

├── _default
│   ├── baseof.html
│   ├── list.html
│   └── single.html
└── index.html

So we’ll start there. Let’s see what each of those templates is about, and then we’ll poke around and see if there are any nice-to-haves beyond these.

Good to know:

The homepage template is the only required template for building a site and therefore useful when bootstrapping a new site and template. It is also the only required template if you are developing a single-page website.

So index.html is the only required template. If you ever watch the hugo server console while linking through your site, you’ll see if you hit a piece of content that doesn’t have a template. If you wanted a single page website, you could get by with something like:

├── config.yaml
├── content
│   └── _index.md
└── layouts
    └── index.html

A complete site. Note: demo a tiny single page site.

The sample index.html template in the docs:


{{ define "main" }}
    <main aria-role="main">
      <header class="homepage-header">
        <h1>{{.Title}}</h1>
        {{ with .Params.subtitle }}
        <span class="subtitle">{{.}}</span>
        {{ end }}
      </header>
      <div class="homepage-content">
        <!-- Note that the content for index.html, as a sort of list page, will pull from content/_index.md -->
        {{.Content}}
      </div>
      <div>
        <!-- Note that .Pages is the same as .Site.RegularPages on the homepage template. -->
        {{ range first 10 .Pages }}
            {{ .Render "summary"}}
        {{ end }}
      </div>
    </main>
{{ end }}

Currently tule’s index.html is:

{{ define "main" }}
    <header>
        <h1>{{- .Title -}}</h1>
    </header>
    {{ .Content }}
{{ end }}

They are actually very similiar, mine is just simplified and excluding a couple of features.

Similarities:

  • define main block
  • header element for title
  • render .Content from content/index.md

Differences:

  • Sample uses many div elements
  • Sample paginates recent pages
  • Sample optionally loads a “subtitle” parameter

There’s the thing about including the main element, but that’s a breakout discussion.

For tule’s purpose, it works. New site’s always have weird page situations going on: folks want to control the look and feel and number and dates of the content showing up on the front page, and this is not the theme for that.

One possible improvement I can think of is rendering the home page even if content/index.md doesn’t exist. I’ve seen other themes with instructions for getting the theme started, and that is a good idea.

Ha! I was testing how it looks without content/index.md, and it just loads the title from config, and otherwise just shows a blank page. I’m fine with that behavior. :slight_smile:

For tule the current baseof.html is:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>{{ block "title" .}}{{- .Title -}}{{ end }}</title>
        <link href="{{ "css/style.css" | absURL }}" rel="stylesheet" type="text/css">
    </head>
    <body>
        <main>
        {{- block "main" . -}}
            <header>
                <h1>{{- .Title -}}</h1>
            </header>
            {{ .Content }}
        {{ end }}
        </main>
    </body>
</html>

I like my elements to be lowercase, so I chose the one of 2048 doctypes that matched my preference. :slight_smile:

html has lang="en" set as I primarily build sites in English. I imagine a more robust theme uses multilingual features to set lang.

The head has those two meta elements that are important, and no more. Note: expand on these elements.

title is the first block I define, as it may change depending on the template. For instance, I may want to elaborate on the paginated pages. Honestly, it’s a bit overkill for tule me thinks.

The link passes the path to absURL, which was a tip I picked up on the forums.

The body and nested document structure is very simple: main element, sensible header, and .Content as default, and able to be overridden in other templates.

I should note here that other templates in fact only override the “main” block, but just copy the same code. For instance, layouts/_default/single.html:

{{ define "main"  }}
<header>
    <h1>{{- .Title -}}</h1>
</header>
{{ .Content }}
{{ end }}

Same for list.html at the moment. Not particular DRY, but I’ve found that the other templates won’t render unless they exist and define blocks. I take advantage of the baseof.html values being default, but for “main” I can safely remove it.

Another missing feature might be a footer. Here’s an interesting place to take advantage of baseof defaults, as I can create a sensible footer, with an easy way to hook into the templates for customizing.

Let’s make those changes and see how it looks.

I removed the “main” block defaults:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>{{ block "title" .}}{{- .Title -}}{{ end }}</title>
        <link href="{{ "css/style.css" | absURL }}" rel="stylesheet" type="text/css">
    </head>
    <body>
        <main>
            {{- block "main" . -}}
            {{ end }}
        </main>
    </body>
</html>

15 lines chrome. Not bad!

I had added a footer element, with a “footer” block, but I couldn’t come up with anything. Not gonna force it!

Something I’ve come to see in creating this theme is that once you have a base, spinning up per-project “themes” makes sense. That means when I need a footer, the project will have a footer (it was four lines, keeping it clean).

Okay, unless I’m missing something big, that’s good enough for the tule baseof.html template. :slight_smile:

A list page template is a template used to render multiple pieces of content in a single HTML page. The exception to this rule is the homepage, which is still a list but has its own dedicated template.

There’s another useful note:

Since section lists and taxonomy lists (N.B., not taxonomy terms lists) are both lists with regards to their templates, both have the same terminating default of _default/list.html or themes/<THEME>/layouts/_default/list.html in their lookup order. In addition, both section lists and taxonomy lists have their own default list templates in _default.

Meaning this template will get used in a lot of instances.

I’ve thought about what I want to include in list.html for tule. I’d like to keep it simple for now, so I’m going to skip pagination. For now. I might add it, or make it another layer that goes on top of tule (tule-pagination).

For my purposes I just want to list some content, to ensure it is working.

  • list page title
  • list page content
  • range of content for that list

Currently, list.html is:

{{ define "main"  }}
<header>
    <h1>{{- .Title -}}</h1>
</header>
{{ .Content }}
{{ end }}

Boom, two out of three already done! We just need to throw in a range for content and we can call it a day. The list template page is basically one long string of examples of how to group and sort and group/sort content in a variety of ways. I will keep it simple for our first go, just range through the content with default sorting, and return a title.

Oh! The first example is so close to what I want in the end, I’ll just copy over the new bits and so:

{{ define "main"  }}
<header>
    <h1>{{- .Title -}}</h1>
</header>
{{ .Content }}
<ul>
{{ range .Pages }}
  <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
  </li>
{{ end }}
</ul>
{{ end }}

I removed the date from the listings, as we don’t actually need that for tule; that can be added back in, with other markup and content, such as article elements rather than list items, and showing a .Summary beneath the title links.

This is just about done, but I want to add one more item: limit the range to 200 entries.

{{ range first 200 .Pages }}
  <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
  </li>
{{ end }}

tule isn’t meant to render list pages for huge repositories of content, but on the off chance I forget that and try to, I won’t kill the build server; it will only list the first 200 pieces of content for any given list page, meaning 200 list item with anchors in them, which probably isn’t that large of a page.

Okay, list.html is done (https://allthe.codes/maiki/tule/commit/d74f93ad8b3c7ea222a596b477c4eeb36bbf2acb). :slight_smile:

1 Like

Returning to tule, I wonder where I want to go from here. Recently I’d been using so many “page builders”, I thought I might create a sub-module for tule that provides options for things like menus and header layouts… but then maybe it could just be config options in the core theme.

I need to decide: do I want tule to be easy to read and modify, or complex enough to meet lots of use cases?

1 Like