Custom Layouts and Template Variables

Authors
Sangeetha Nandakumar | Nicholas Del Grosso

Hugo builds websites by reading content files and layout templates. Layouts are HTML files that control how content appears. Without a theme, layouts must be created manually.

Here we will see:

  • how to create the homepage layout
  • how to add HTML structure around content
  • how to use HTML tags

Section 1: Creating the Homepage Layout

Background

Hugo cannot display pages without layout templates. The homepage layout must be created at layouts/index.html. The simplest layout contains the {{ .Content }} placeholder, which displays markdown content from content/_index.md.

Exercises

This section covers creating the homepage layout file and understanding how HTML wraps around content.

Concept Description
layouts/index.html Layout file for homepage
{{ .Content }} Placeholder for markdown content
<h1>, <h2> Heading tags
<p> Paragraph tag
<a href=""> Link tag
<hr> Add horizontal line

The homepage layout contains HTML structure with the content placeholder. The basic HTML structure includes <!DOCTYPE html>, <html>, <head>, and <body> tags.

Example: Create layouts/index.html with the main skeleton and content placeholder

<html>
<head>
    <title>My Portfolio</title>
</head>
<body>
    {{ .Content }}
</body>
</html>

Exercise: Display the content inside an <h1> heading instead of directly in the body.

Solution
<html>
<head>
    <title>My Portfolio</title>
</head>
<body>
    <h1>{{ .Content }}</h1>
</body>
</html>

Exercise: Change the heading from <h1> to <h2> .

Solution
<html>
<head>
    <title>My Portfolio</title>
</head>
<body>
    {{ .Content }}
</body>
</html>

Exercise: Remove the <h2> heading and bring the content placeholder back to display directly in the body.

Solution
<html>
<head>
    <title>My Portfolio</title>
</head>
<body>
    {{ .Content }}
</body>
</html>

HTML can be added before and after {{ .Content }} to create headers, footers, and other elements. These appear on every page using this layout.

Example: Add a heading before the content placeholder:

<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
</body>

Exercise: Add a footer paragraph with copyright text after the content placeholder.

Solution
<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
    <p>© 2025 All rights reserved</p>
</body>

Exercise: Add a horizontal line (<hr>) before the footer.

Solution
<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Links use the <a> tag with the href attribute. The text between <a> and </a> appears as the clickable link.

Example: Below the footer, add a link to contact email:

<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <p><a href="mailto:contact@example.com">Email Me</a></p>
</body>

Exercise: Add a GitHub link next to the email link.

Solution
<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <p>
        <a href="mailto:contact@example.com">Email Me</a> | 
        <a href="https://github.com/username">GitHub</a>
    </p>
</body>

Exercise: Add a LinkedIn link after the GitHub link.

Solution
<body>
    <h1>Research Portfolio</h1>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <p>
        <a href="mailto:contact@example.com">Email Me</a> | 
        <a href="https://github.com/username">GitHub</a> | 
        <a href="https://linkedin.com/in/username">LinkedIn</a>
    </p>
</body>

Section 2: Using Template Variables

Background

Hugo can read configuration values from hugo.toml and display them in layouts using template variables. Variables are accessed with {{ .Site.Params.variablename }}. This allows changing site information without editing HTML files.

Exercises

This section covers accessing configuration variables from hugo.toml and using conditional statements to display optional content.

Concept Description
{{ .Site.Params.name }} Access parameter from hugo.toml
{{ if .Site.Params.name }} Check if parameter exists
{{ end }} End if statement
[params] Section in hugo.toml for custom parameters

Variables in hugo.toml are defined under the [params] section. These can be accessed in layouts and changed without modifying HTML.

Example: Add and access author variable from hugo.toml. Display the author name below the title.

First, add to hugo.toml:

[params]
author = "Dr. Jane Smith"

Then update layouts/index.html:

<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Exercise: Add and access tagline variable from hugo.toml. Display it below the author name.

Add to hugo.toml:

Solution
[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"

Update layouts/index.html:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Exercise: Change the tagline in hugo.toml to a different value and verify it updates on the page.

Solution
[params]
author = "Dr. Jane Smith"
tagline = "Machine Learning Researcher"

Contact information can be stored in variables instead of hardcoded in HTML. This makes it easier to update across the site.

Example: Update email to access the email variable from hugo.toml.

Add to hugo.toml:

[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"
email = "jane.smith@university.edu"

Update layouts/index.html:

<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <p><a href="mailto:{{ .Site.Params.email }}">Email Me</a></p>
</body>

Exercise: Update GitHub and LinkedIn links to access variables from hugo.toml.

Add to hugo.toml:

Solution
[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"
email = "jane.smith@university.edu"
github = "https://github.com/janesmith"
linkedin = "https://linkedin.com/in/janesmith"

Update layouts/index.html:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <p>
        <a href="mailto:{{ .Site.Params.email }}">Email Me</a> | 
        <a href="{{ .Site.Params.github }}">GitHub</a> | 
        <a href="{{ .Site.Params.linkedin }}">LinkedIn</a>
    </p>
</body>

Exercise: Add headings to all the links showing the platform name and the link.

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <h2>Contact</h2>
    <p>
        <a href="mailto:{{ .Site.Params.email }}">Email Me</a> | 
        <a href="{{ .Site.Params.github }}">GitHub</a> | 
        <a href="{{ .Site.Params.linkedin }}">LinkedIn</a>
    </p>
</body>

Conditional statements display content only when a variable exists. The {{ if }} statement checks if a parameter is defined. This is useful for optional social media links.

Example: Add X (Twitter) link if available using an if condition.

Add to hugo.toml:

[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"
email = "jane.smith@university.edu"
github = "https://github.com/janesmith"
linkedin = "https://linkedin.com/in/janesmith"
x = "https://x.com/janesmith"

Update layouts/index.html:

<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <h2>Contact</h2>
    <p>
        <a href="mailto:{{ .Site.Params.email }}">Email Me</a> | 
        <a href="{{ .Site.Params.github }}">GitHub</a> | 
        <a href="{{ .Site.Params.linkedin }}">LinkedIn</a>
        {{ if .Site.Params.x }}
        | <a href="{{ .Site.Params.x }}">X</a>
        {{ end }}
    </p>
</body>

Exercise: Add Instagram link if available using an if condition.

Add to hugo.toml:

Solution
[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"
email = "jane.smith@university.edu"
github = "https://github.com/janesmith"
linkedin = "https://linkedin.com/in/janesmith"
x = "https://x.com/janesmith"
instagram = "https://instagram.com/janesmith"

Update layouts/index.html:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <h2>Contact</h2>
    <p>
        <a href="mailto:{{ .Site.Params.email }}">Email Me</a> | 
        <a href="{{ .Site.Params.github }}">GitHub</a> | 
        <a href="{{ .Site.Params.linkedin }}">LinkedIn</a>
        {{ if .Site.Params.x }}
        | <a href="{{ .Site.Params.x }}">X</a>
        {{ end }}
        {{ if .Site.Params.instagram }}
        | <a href="{{ .Site.Params.instagram }}">Instagram</a>
        {{ end }}
    </p>
</body>

Exercise: Add Facebook link if available. Test by adding and removing the Facebook link in hugo.toml.

Add to hugo.toml:

Solution
[params]
author = "Dr. Jane Smith"
tagline = "Computational Biologist & Data Scientist"
email = "jane.smith@university.edu"
github = "https://github.com/janesmith"
linkedin = "https://linkedin.com/in/janesmith"
x = "https://x.com/janesmith"
instagram = "https://instagram.com/janesmith"
facebook = "https://facebook.com/janesmith"

Update layouts/index.html:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    <hr>
    <p>© 2025 All rights reserved</p>
    <h2>Contact</h2>
    <p>
        <a href="mailto:{{ .Site.Params.email }}">Email Me</a> | 
        <a href="{{ .Site.Params.github }}">GitHub</a> | 
        <a href="{{ .Site.Params.linkedin }}">LinkedIn</a>
        {{ if .Site.Params.x }}
        | <a href="{{ .Site.Params.x }}">X</a>
        {{ end }}
        {{ if .Site.Params.instagram }}
        | <a href="{{ .Site.Params.instagram }}">Instagram</a>
        {{ end }}
        {{ if .Site.Params.facebook }}
        | <a href="{{ .Site.Params.facebook }}">Facebook</a>
        {{ end }}
    </p>
</body>

Test by removing the Facebook line from hugo.toml and verifying the link disappears from the page.

Section 3: Working with Data Files

Background

Hugo can read data from YAML files stored in the data/ directory. These files contain structured information that can be displayed using loops in layouts. Data files are useful for lists like skills, projects, or publications that need to be updated frequently.

Exercises

This section covers creating data files and using loops to display their content.

Concept Description
data/filename.yaml Data file in YAML format
{{ range .Site.Data.filename }} Loop through data items
{{ .fieldname }} Access field from current item
{{ end }} End loop
<ol>, <ul> Ordered and unordered list tags

Data files use YAML format with items listed using hyphens. Each item can have one or more fields.

Example: Add two skills in data/skills.yaml and use loops to display them in a Skills section after Content. Use an ordered list.

Create data/skills.yaml:

- Python
- R

Update layouts/index.html to add a Skills section after Content:

<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    
    <h2>Skills</h2>
    <ol>
    {{ range .Site.Data.skills }}
        <li>{{ . }}</li>
    {{ end }}
    </ol>
    
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Exercise: Add three more skills in data/skills.yaml and change the list to unordered.

Update data/skills.yaml:

Solution
- Python
- R
- JavaScript
- SQL
- C++

Update layouts/index.html to use <ul> instead of <ol>:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    
    <h2>Skills</h2>
    <ul>
    {{ range .Site.Data.skills }}
        <li>{{ . }}</li>
    {{ end }}
    </ul>
    
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Exercise: Create data/projects.yaml and use loops to display projects in an ordered list.

Create data/projects.yaml:

Solution
- Data Pipeline
- Visualization Tool
- Analysis Framework

Update layouts/index.html to add Projects section:

Solution
<body>
    <h1>Research Portfolio</h1>
    <p>By {{ .Site.Params.author }}</p>
    <p>{{ .Site.Params.tagline }}</p>
    {{ .Content }}
    
    <h2>Skills</h2>
    <ul>
    {{ range .Site.Data.skills }}
        <li>{{ . }}</li>
    {{ end }}
    </ul>
    
    <h2>Projects</h2>
    <ol>
    {{ range .Site.Data.projects }}
        <li>{{ . }}</li>
    {{ end }}
    </ol>
    
    <hr>
    <p>© 2025 All rights reserved</p>
</body>

Data items can have multiple fields. Fields are defined with a name and value separated by a colon.

Example: Add a level field for each skill and display it as “Skill - level”.

Update data/skills.yaml:

- name: Python
  level: Advanced
  
- name: R
  level: Intermediate
  
- name: JavaScript
  level: Beginner
  
- name: SQL
  level: Advanced
  
- name: C++
  level: Beginner

Update layouts/index.html to display name and level:

<h2>Skills</h2>
<ul>
{{ range .Site.Data.skills }}
    <li>{{ .name }} - {{ .level }}</li>
{{ end }}
</ul>

Exercise: Add years of experience to each skill and display as “Skill - level - years exp”.

Update data/skills.yaml:

Solution
- name: Python
  level: Advanced
  years: 5
  
- name: R
  level: Intermediate
  years: 3
  
- name: JavaScript
  level: Beginner
  years: 1
  
- name: SQL
  level: Advanced
  years: 4
  
- name: C++
  level: Beginner
  years: 1

Update layouts/index.html:

Solution
<h2>Skills</h2>
<ul>
{{ range .Site.Data.skills }}
    <li>{{ .name }} - {{ .level }} - {{ .years }} years exp</li>
{{ end }}
</ul>

Exercise: Add description field to items in data/projects.yaml and display both title and description.

Update data/projects.yaml:

Solution
- title: Data Pipeline
  description: Automated processing system for research data
  
- title: Visualization Tool
  description: Interactive dashboard for experimental results
  
- title: Analysis Framework
  description: Statistical analysis toolkit for genomic data

Update layouts/index.html:

Solution
<h2>Projects</h2>
<ol>
{{ range .Site.Data.projects }}
    <li>
        <strong>{{ .title }}</strong>
        <p>{{ .description }}</p>
    </li>
{{ end }}
</ol>