Custom Layouts and Template Variables
Authors
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
- RUpdate 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 FrameworkUpdate 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: BeginnerUpdate 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: 1Update 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 dataUpdate layouts/index.html:
Solution
<h2>Projects</h2>
<ol>
{{ range .Site.Data.projects }}
<li>
<strong>{{ .title }}</strong>
<p>{{ .description }}</p>
</li>
{{ end }}
</ol>