Hugo Single Page Templates

Janne Kemppainen |

Now that our blog has a basic single post template it is time to move on to other content pages. In this post we will concentrate on pages such as About, Privacy Policy and Contact.

Even though the site itself is static it is possible to embed dynamic content from other sources. For example, the contact page will show you how to embed Google Forms as a contact form and a map showing your office location.

I assume that you are continuing from the situation in the previous posts and have the Hugo development server running locally.

General page template

If you check the layouts/_default/single.html file inside your theme you'll see that it is still empty. This is the file that Hugo will fall back to if it can't find anything more suitable for a single page. Now, because it doesn't contain the main-block Hugo won't be able to render single pages.

Let's fix this by defining a simple main block inside the template file.

{{ define "main" }}
<section class="section">
    <div class="container max-800px">
        <h1 class="title is-3">
            {{ .Title }}
        </h1>
        <div class="content">
            {{ .Content }}
        </div>
    </div>
</section>
{{ end }}

Now all single pages will default to this simple layout. Here I used the max-800px class that was defined in the previous blog post of this series to limit the width of the content.

The About page

About is the page where your users can see what your site is about, who it is for, what value do you give your readers, etc. It is the page where people go to when they want to know more about your website.

Create the about page Markdown file by running the following Hugo command at the root of your project:

>> hugo new about.md

Open the created file content/about.md in an editor and remove the draft: true line so that the page will actually be published when you go live. Add some Markdown content and go to http://localhost:1313/about/ to see how it looks.

Create a custom template

Let's create a custom template for the about page so that we don't use the default one. Create a new template file layouts/_default/about.html inside the theme. Add an empty define “main” block to start with.

This isn't enough to make hugo use the new layout file yet. You need to set the desired layout explicitly in the front matter by adding the following line in the about.md file

layout: about

The existing page content should disappear as there is the template file we are now using doesn't have any markup yet.

I'm thinking about creating a full height hero image with the page title with the rest of the content being below the fold. I will be using this photo from pexels.com. I've cropped the image a bit as shown below to have a better fit on all browser sizes.

Blog author image

Placeholder image for the About page

As always, start by defining the main block.

{{ define "main" }}

{{ end }}

Add the hero element

Bulma has the Hero element that we can use to display the full height background image with the page title. The hero element is especially handy as it can take into account the height of the navbar so that the hero block ends at the bottom edge of the browser window.

So let's start by defining the hero element:

{{ define "main" }}
<div class="hero is-dark is-fullheight-with-navbar" {{ with .Params.heroimage }}
    style="background: url({{ . }}) center top; background-size:cover;" {{ end }}>
    <div class="hero-body">
    </div>
</div>
{{ end }}

Here we added two divs to the page. The first one is the Hero element as it has the hero class. The is-dark changes the background color to dark gray and the is-fullheight-with-navbar does exactly what you'd expect; it sets the div height equal to the viewport height minus navbar height.

Next there is some Hugo templating to set the background image. Because modifying CSS on the fly is really not an option as CSS files are stored in the static directory I'm using inline styles to set images that I want to be defined from the page front matter.

In this case Hugo will search for the heroimage parameter in the front matter and only if it is found will it set the inline style. As within the with clause the image parameter is the current context we can use dot to place it inside the url() CSS function. The image is also vertically centered and horizontally top aligned. The background-size setting makes the image cover the whole area even if the image aspect ratio doesn't match the browser window.

Right now the hero block is all gray as we haven't defined the heroimage parameter yet. Download my example image or use your own and save it to the static/images directory of your website. Now reference that image by adding the following line to the front matter of about.md:

heroimage: /images/author.jpg

The background image of the rendered About page should change. Now you can also add some test content such as headings and text to the Markdown file so that you have something ready when we add the content part.

Hero content

The hero block contains the hero-body element which has the main content of the block. This is where we will put the page title and a custom text block if you so desire. Make the hero body look like this:

<div class="hero-body">
    <div class="container max-800px">
        <h1 class="title is-1 has-background-primary narrow-background">
            {{ .Title }}
        </h1>
        {{ with .Params.herotext }}
        <h2 class="title subtitle is-4 has-background-primary narrow-background">
            {{ . }}
        </h2>
        {{ end }}
    </div>
</div>

The first thing inside is a container div where we also used the max-800px class which limits the width of the content to 800 pixels. We defined this helper class in the previous post so go check that out if you don't yet have it.

Next we add the page title from the front matter with a large font size and using the primary background color of Bulma.

There is also a new helper class narrow-background which you should add to the style.css file:

.narrow-background {
    display: table;
    padding: 0.1em;
}

Without this helper class the title would expand to the full width of the parent container which would look ugly with the background color. The display: table fixes this by making the title element take only the space that it requires. The small padding gives just a tiny bit of headroom so that the background doesn't start from the edges of the letters.

The second title is a smaller one and it is optional. The with block looks for a parameter herotext in the front matter and adds it as the subtitle if found. You can add some motto or other motivational text here to summarize what your site is about.

Page content

Finally, add the actual content after the hero div:

<div class="section">
    <div class="container max-800px">
        <div class="content">
            {{ .Content }}
        </div>
    </div>
</div>

This uses the same max-800px class so that the user experience is the same as with the blog posts.

I'm using the following front matter in the blog.md front matter.

---
title: "About Me"
date: 2019-04-26T20:18:54+03:00
heroimage: /images/author.jpg
herotext: Helping you succeed in life
layout: about
---

And the actual rendered page looks like this:

About page example rendered

About page example

Note that you can compile the Bulma styles with custom colors so you can actually change the primary background color later.

Privacy policy

Next, let's create the privacy policy for your site. There are many online privacy policy generators available so choose the one you like to create your own policy. Usually they will not provide the result in Markdown format so you'll have to either convert the text manually, or you can use the HTML directly.

There is also a privacy notice template available from the EU GDPR website that you can modify to suit your needs.

Create the privacy page with Hugo

>> hugo new privacy.md

Open the generated file and change the title to “Privacy Policy” in the front matter. Add your policy text and save the file.

If you now navigate to the privacy page at http://localhost:1313/privacy you'll see that the page used the default layout for single pages which actually looks ok for this kind of text-only page.

Contact page

The last part of this post is to create a page where your visitors can contact you. Contact pages can have a wide variety of information so what I'm showing here should be considered as inspiration and adjusted as needed.

Layout

The first thing we should do is create a new template for the contact page so create the required template file layouts/_defaults/contact.html and define the main block

{{ define "main" }}
{{ end }}

Next create the actual content file:

>> hugo new contact.md

Edit the front matter to use the correct layout and remove the draft line. You can also change the page title.

---
title: "Contact us"
date: 2019-04-29T11:58:46+03:00
layout: contact
---

Open the site configuration file config.toml and add the contact page to the navbar or footer.

[[menu.main]]
    name = "Contact"
    url = "/contact"

Now the site visitors have a way to navigate to the contact page.

Let's use a bit similar layout as we did with the about page but this time let's not use a full height hero image. I'll be using the image below as the hero background.

Contact page background image

Placeholder image for the Contact page

First add the code below to the template file to create the hero element. It is using the same trick as with the About page for the background image but this time the image is centered in both directions. The page title is also contained inside the hero head so it is located at the top of the hero element. This works better for the image that I selected as the people are not obscured by the text. Feel free to move it inside the hero-body block which I left empty in this case.

<div class="hero is-dark is-medium" {{ with .Params.heroimage }}
    style="background: url({{ . }}) center center; background-size:cover;" {{ end }}>
    <div class="hero-head">
        <div class="container">
            <h1 class="title is-2 has-background-primary narrow-background">
                {{ .Title }}
            </h1>
            {{ with .Params.herotext }}
            <h2 class="title subtitle is-4 has-background-primary narrow-background">
                {{ . }}
            </h2>
            {{ end }}
        </div>
    </div>
    <div class="hero-body"></div>
</div>

Next, add the following block of code after the hero-body div inside the hero block.

<div class="hero-foot">
    {{ with .Params.buttons }}
    <nav class="tabs">
        <div class="container">
            <ul>
                {{ range . }}
                <li>
                    <a class="has-background-primary" {{ with .id }}id="{{ . }}" {{ end }}
                        {{ with .href }}href="{{ . | safeURL }}" {{ end }}>
                        <span class="icon"><i class="{{ .icon }}" aria-hidden="true"></i></span>
                        <span>{{ .name }}</span>
                    </a>
                </li>
                {{ end }}
            </ul>
        </div>
    </nav>
    {{ end }}
</div>

This places content at the bottom part of the hero element, the hero footer, if the page has a parameter called buttons defined. This is a list of dictionaries that have at least a name and an icon but which can also specify the href or the id. If buttons is defined then a list of tabs is added to the page according to the parameters.

Below are some example values that you can use in your front matter to try out. The first one is for adding a phone link that starts a call to your phone number and the second one is for opening a contact form that we will define later in this post. Notice the safeURL function in the template which allows us to add the tel: part to the href.

buttons:
- name: Phone
  icon: fas fa-phone
  href: "tel:555555555"
- name: Contact form
  icon: fas fa-envelope
  id: open-modal

The Bulma tabs are not responsive on mobile devices so let's add some CSS that remove the text on smaller screens and only shows the icons. As these modifications are specific to this page you can add the CSS inside the template file with <style> tags.

<style>
    @media screen and (max-width: 768px) {
        li>a>span:not(.icon) {
            visibility: hidden;
            position: absolute;
        }
        nav.tabs li {
            -webkit-box-flex: 0;
            -ms-flex-positive: 0;
            flex-grow: 0;
            -ms-flex-negative: 1;
            flex-shrink: 1;
        }
        .tabs .icon {
            margin-left: 0.5em;
        }
    }
</style>

Page content and maps

Add a new section after the hero element and inside it a container for the main content of the site. This time we wont be limiting the page width to 800 pixels.

The code below has the content block that we are already used to having on our pages but in addition to it there is also a conditional block that is added if a parameter called map is defined. This block adds an embedded Google Maps iframe to a column that takes one third of the available space, leaving two thirds for the content block.

<div class="section">
    <div class="container">
        <div class="columns">
            <div class="column">
                <div class="content">
                    {{ .Content }}
                </div>
            </div>
            {{ with .Params.map }}
            <div class="column is-one-third">
                <div class="map-responsive">
                    <iframe
                        src="https://maps.google.com/maps?hl=en&amp;q={{ . | htmlEscape }}&amp;ie=UTF8&amp;t=&amp;z=15&amp;iwloc=B&amp;output=embed"
                        frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
                </div>
            </div>
            {{ end }}
        </div>
    </div>
</div>

I modified the idea from this page to generate a responsive Google Maps embed code. As the styling is specific to this page only you can add the following CSS inside <style> tags on the page template.

    .map-responsive {
        overflow: hidden;
        padding-bottom: 100%;
        position: relative;
        height: 0;
    }

    .map-responsive iframe {
        left: 0;
        top: 0;
        height: 100%;
        width: 100%;
        position: absolute;
    }

What's neat about this solution is that you only need to specify the address or other place in the front matter of the page as normal text and the Hugo block will actually change it to a HTML query with the htmlEscape function. For example, to set the map to the Helsinki central library Oodi the following line is enough:

 
map: Oodi, Helsinki

Remember that you can add pure HTML to the Markdown files as well. Here is a mix and match of some example content for your contact page. You can modify it as needed.

<div class="columns is-multiline is-mobile">
    <div class="column">
        <h2 class="title is-4">Opening hours</h2>
        <p>Mon-Fri: 10:00-20:00<br>
        Sat: 10:00-18:00<br>
        Sun: closed</p>
    </div>
    <div class="column">
        <h2 class="title is-4">Address</h2>
        <p>My Company <br>Töölönlahdenkatu 4 <br>00100 Helsinki<br>Finland</p>
    </div>
</div>

### Our services
- We can do this
- We can do that
- We can do anything

And here is how the final result looks like.

Contact page layout rendered

Contact page layout rendered on browser

And on mobile:

Contact page layout rendered

Contact page layout on a mobile resolution

As you can see the call and message buttons have changed to show only the icons to preserve space.

Contact form

Because our site doesn't have an active server we can't really utilize a traditional contact form. However, we can use external services such as Google Forms for this kind of dynamic functionality. (Note that we could use Netlify forms but I'm keeping this part of the tutorial more general.)

We already added the contact form open button with the id open-modal by defining it in the front matter. Now it's time to put it to use. Here is the full code to embed a modal Google Form to your page:

{{ with .Params.form }}
<style>
    .contact-form {
        width: 640px;
        height: 960px;
        overflow: scroll;
    }
</style>
<div class="modal">
    <div class="modal-background"></div>
    <div class="modal-content" style="overflow: auto!important; -webkit-overflow-scrolling: touch!important;">
        <iframe class="contact-form" src="{{ . }}" width="640" height="1019" frameborder="0" marginheight="0"
            marginwidth="0">Loading...</iframe>
    </div>
    <button class="modal-close is-large" aria-label="close"></button>
</div>
<script>
    document.querySelector('a#open-modal').addEventListener('click', function (event) {
        event.preventDefault();
        var modal = document.querySelector('.modal');
        var html = document.querySelector('html');
        modal.classList.add('is-active');
        html.classList.add('is-clipped');

        var close = (e) => {
            e.preventDefault();
            modal.classList.remove('is-active');
            html.classList.remove('is-clipped');
        }
        modal.querySelector('.modal-background').addEventListener('click', close);
        modal.querySelector('.modal-close').addEventListener('click', close);
    });
</script>
{{ end }}

The only thing you need to add to the Markdown file is to add a string form that defines the embed link to your contact form.

form: https://docs.google.com/forms/d/e/1FAIpQLScJu093Bj9AS6ryW9KWgrbMx6vYZF2dxk_vlboINkKqfRU83A/viewform?embedded=true

You can get this from the Google Forms page after creating a form by clicking SEND and selecting the embed option. Then, instead of copying the full embed code just select the URL from the src attribute and paste it to the front matter as above.

The code is split in three parts: style, Bulma modal and a jQuery script. All of this is added only if the form parameter has been set in the front matter. The style part makes the embedded iframe show properly on the page as without this only a part of the embedded form would be visible without scrolling.

The HTML part uses the Bulma modal to create a new modal element. The modal-background div represents the dark gray background overlay and the modal-content has the actual content at the front. It then contains the embedded iframe from Google Forms. There is also a button to close the modal form which is placed in the top right corner.

The script adds an event listener to the open-modal element that we defined earlier and upon clicking it adds the is-active class to the modal element which makes it visible. The is-clipped class on the <html> root tag disables scrolling on the page by setting overflow to hidden. Clicking either the modal background or the close button closes the modal view.

The form should look like this when opened:

Modal view opened

Modal contact form opened

Conclusion

Now you know how to create single pages to your Hugo site. You also know how to specify special layouts for different pages with the layout parameter in the front matter. This should give you the tools to create pages that are specific to your site.

Subscribe to my newsletter

What's new with PäksTech? Subscribe to receive occasional emails where I will sum up stuff that has happened at the blog and what may be coming next.

powered by TinyLetter | Privacy Policy