Toph: a lightweight, responsive theme for a biography site, for use with Hugo static site generator.
Hugo 0.161.0 or later is required. Toph uses Hugo's
css.Buildfunction withvarsto inject CSS custom properties at build time. Older Hugo versions will fail to build.
- Built with Bootstrap 5
- Responsive-ready
- Template-driven layout system
- Shortcodes for rich content embeds (PDF download, tweet archive)
- Modular CSS architecture with customizable color palette
- Navbar brand with site title (responsive, centered on mobile)
- RSS feeds at
/rss/with site name in titles and autodiscovery in<head> - Multilingual support (English, Spanish, Arabic with RTL, Hindi)
- First-class support for both Markdown and AsciiDoc content
- Search engine optimization: meta description, canonical URL, hreflang, structured data (WebSite, Person, Article, BreadcrumbList JSON-LD)
- 404 page with localized "Return to homepage" link
- Easy customization of colors and fonts in Hugo configuration
The recommended installation method for an existing Hugo site is with a git submodule. Use the commands below to add the theme to an existing Hugo site:
cd /path/to/hugo-site/
mkdir themes/
git submodule add git@github.com:justwheel/toph-hugo-theme.git themes/toph
git commit --signoff --message="Add Toph theme as a git submodule"
git pushSometimes you will want to update the git submodule with new changes added upstream to this repository. To pull newer upstream changes into your pre-existing git submodule, run the following from your Hugo project root directory:
git submodule update --remote --rebaseToph includes a project profile feature. Project profiles are shown on the main index page along with an image or logo. Use these project profiles to showcase the primary content about the person, organization, or whoever/whatever the biography site is for. The name is projects, but it can cover any type of content you wish, but you must use the correct metadata.
- Create a new "projects" section.
- Create a new AsciiDoc or Markdown file for each project profile.
- In each profile, add the following to the front matter:
title: ""— Title or name for the profile. This is used as the header for each profile.date: YYYY-MM-DD— Projects must have a date. This is used for sequencing, i.e. how the projects are ordered on the index page. Give older projects a date further in the past, and newer projects a date closer to the present. Projects will be sorted in descending order.slug: ""— Determines the anchor of the title header. This is handy for quick links to a specific project on the page, e.g.example.com#project-alphaicon: ""— Corresponding image for the project. The image appears in two places: underneath the title header, and in a carousel of images at the top of the page, underneath the index page content but before the project profiles.hide_sitemap: true— Do not add the fully-qualified link for this page to the sitemap. Prevents search engines from indexing "hidden" pages that are already shown as part of the site index page content.categories: ["projects"]— Required to set all project profiles to the "projects" category for the template to work. If the category is unset or incorrect, project profiles will not display.
- Rebuild the site. The site index page now shows the project profiles.
Example of a project profile, found in content/projects/:
---
title: "Open@RIT"
date: 2020-09-09
slug: open-rit
icon: /img/rit.png
hide_sitemap: true
categories: ["projects"]
---
In September 2020, I [joined][1] the Open@RIT Advisory Board.
[Open@RIT][2] is a Key Research Center of the [Rochester Institute of Technology][3] and serves as the Open Source Programs Office for RIT.
Its goals are to discover and grow RIT's impact on all things Open including, but not limited to, Open Source Software, Open Data, Open Hardware, Open Educational Resources and Creative Commons-licensed efforts.
Otherwise what we like to refer to in aggregate as _Open Work_.
[1]: https://x.com/jflory7/status/1303783881582104577
[2]: https://www.rit.edu/research/open
[3]: https://www.rit.edu/Toph includes a team profile feature for displaying a group of people alongside the site's primary biography.
Team members appear in a responsive card grid on the homepage and on a dedicated /team/ section page.
Photos are rendered as circular, grayscale images using Hugo image processing.
- Create a new
content/team/section. - Create a
_index.md(or_index.en.md) file with a title and optional introductory text. - Create a new Markdown or AsciiDoc file for each team member with a numbered prefix for ordering (e.g.,
1-first-person.en.md). - In each team member file, add the following front matter:
title: ""— Name of the team member.date: YYYY-MM-DD— Used for ordering. Members are sorted by date in descending order.hide_sitemap: true— Prevents the individual page from appearing in the sitemap.categories: ["team"]— Required for the template to identify team content.link: ""— Optional external biography URL. If set, the member's name links to this URL. If omitted, the name links to the member's Hugo page.photo: ""— Optional image path. Local asset paths (e.g.,pages/team/photo.jpg) are processed by Hugo (square crop + WebP conversion). Remote URLs (https://...) and absolute paths (/img/...) are rendered as-is.role: ""— Short descriptor displayed under the name (e.g., "Lead Engineer").title_text: ""— Optional text for the image'stitleattribute (displayed on hover). Useful for photo attribution or captions.years: ""— Lifespan or active years, displayed as metadata.
- Add
"team"toparams.taxonomy_excludein your Hugo config. - Optionally set
params.team.page_titleto customize the section heading (default: "Team"). - Rebuild the site.
The homepage now shows the team grid, and
/team/shows the full section page.
Example of a team member file, found in content/team/:
---
title: "Ada Lovelace"
slug: ada-lovelace
date: 1852-11-27
hide_sitemap: true
categories: ["team"]
link: "https://en.wikipedia.org/wiki/Ada_Lovelace"
photo: "pages/team/ada-lovelace.jpg"
role: "Mathematician & Writer"
title_text: "Public domain portrait, 1840."
years: "1815 – 1852"
---
Ada Lovelace is widely regarded as the first computer programmer.
She wrote the first algorithm intended to be processed by a machine.Toph can show affiliation or supporter badges in the footer of the site. The footer badges are images, meant to imply an endorsement or message of support to another website or organization. Use these to note endorsements, sponsors, or some other badge to include in the site footer.
- Create a new "footers" section.
- Create a new AsciiDoc or Markdown file for each badge.
- In each badge, add
categories: ["footer"]andhide_sitemap: trueto the front matter. - Add one and only one image to the file. Images may be wrapped by an external hyperlink.
- Rebuild the site. The footer now shows the footer badges.
Example of a dynamic footer badge, found in content/footer/:
---
categories: ["footer"]
hide_sitemap: true
---
[link=https://sfconservancy.org/sustainer/]
image::https://sfconservancy.org/img/supporter-badge.png[Become a Conservancy Sustainer!]The height and width of all badges is fixed. Keep in mind these requirements when adding new footer badges.
- DPI: 25.40 dpi
- Height: 90 pixels
- Width: 194 pixels
- Style suggestions:
- Add a light-gray (RGBA:
#272b35ff) border around the badge.
- Add a light-gray (RGBA:
Toph includes a full-featured blogging system with taxonomy support, pagination, and content discovery.
- Blog archive — posts grouped by year and month in a collapsible accordion at
/blog/ - Categories — magazine-style card grid with images, descriptions, and recent posts at
/categories/ - Tags — visual word cloud with font-size/opacity scaling by post count at
/tags/ - Post metadata — publication date, author, reading time, word count, and taxonomy badges
- Post navigation — previous/next links at the bottom of each post
- Recent posts — the 5 most recent posts displayed on the homepage
- Cover images — optional
imagesfront matter (Hugo's standard.Params.images) for a featured image and automatic OpenGraph/Twitter card previews - Image captions —
renders as<figure>/<figcaption> - Heading anchors — clickable 🔗 links on section headings for sharing direct URLs
- Pagination — configurable page size via
pagination.pagerSizein config - Configurable date format — set
params.date_formatto any Go time format (default:"2006 January 02") - Configurable footer license — set
params.legal.licensewithname,url, andtitlefields - RSS feed — custom template with full post content, served at
/rss/with autodiscovery<link>in<head>. Posts with cover images include the image as both an inline<img>and an RSS 2.0<enclosure>element with file size and MIME type - AsciiDoc support — all features work identically for both Markdown and AsciiDoc content
hugo new blog/my-first-post.mdThis uses the blog.md archetype which scaffolds the standard front matter fields.
Set draft: false when ready to publish.
Add the following to your Hugo configuration to enable taxonomy filtering and pagination:
taxonomies:
category: categories
tag: tags
pagination:
pagerSize: 10
params:
taxonomy_exclude: ["footer", "projects", "team"]
layouts:
categories:
recent_posts: 3
excerpt_length: 250Toph includes comprehensive SEO features that work automatically with minimal configuration.
- Meta description — uses the page's
descriptionfront matter field, falling back toparams.descriptionin the site config. Google uses this as the primary source for search result snippets. - Canonical URL — automatically set to each page's permalink. Prevents duplicate content issues, especially on multilingual sites.
- hreflang alternate links — automatically generated for translated pages so search engines know the relationship between language versions.
- OpenGraph and Twitter Cards — Hugo's built-in templates are included automatically.
The site title (site.Title) appears in the navbar on every page.
On mobile, the brand is centered in the navbar bar.
On desktop, it flows inline as a standard Bootstrap navbar-brand element.
Long titles are protected with max-width and text-overflow: ellipsis.
The homepage <title> tag appends the biography tagline if configured:
Justin Wheeler — Building the human infrastructure of open source
Set params.biography.tagline in your Hugo config.
If no tagline is set, the title is just the site name.
Toph generates Schema.org structured data as JSON-LD in the <head> of every page:
| Schema | Pages | Purpose |
|---|---|---|
| WebSite | Homepage only | Identifies the site as a named entity with a URL and description |
| Person | All pages | Site owner identity with name, social profiles, expertise, and optional job title / employer |
| Article | Blog posts only | Headline, dates, author, publisher, description, and cover image for rich search results |
| BreadcrumbList | Non-home, non-404 pages | Navigation hierarchy (2 levels for top-level pages, 3 for section content) |
Add these to params.biography in your Hugo config to enrich the Person schema:
params:
biography:
job_title: "Fedora Community Architect"
employer: "Red Hat"Both fields are optional and guarded with {{ with }} — they are omitted from the JSON-LD if not configured.
Toph provides shortcodes for embedding rich content in blog posts and pages. Shortcodes work in both Markdown and AsciiDoc content.
The pdf-download shortcode renders a styled download card for local PDF files.
The card displays a PDF icon, document title, optional description, auto-computed file size, and a download button.
| Parameter | Required | Description |
|---|---|---|
file |
Yes | Path to the PDF file in static/ (e.g., /docs/report.pdf) |
title |
Yes | Display title for the document |
description |
No | Brief summary of the PDF content |
{{< pdf-download file="/docs/report.pdf" title="Annual Report 2025" >}}With an optional description:
{{< pdf-download
file="/docs/report.pdf"
title="Annual Report 2025"
description="Full financial and operational summary for fiscal year 2025." >}}File size is computed at build time from the file in static/ and displayed in SI units:
- Under 1 MB: integer KB (e.g., "54 KB")
- 1 MB to 999 MB: one decimal MB (e.g., "2.3 MB")
- 1 GB and above: one decimal GB (e.g., "1.5 GB")
The tweet-archive shortcode embeds a self-hosted archived tweet as a styled card.
Each archived tweet is a Hugo page bundle with its own dedicated URL, images, and metadata — no external dependencies or JavaScript embeds from Twitter/X.
This is designed for preserving tweets from deleted or inaccessible accounts while maintaining full control over the content and SEO.
- Styled card with author avatar, display name, handle, and "Archived" badge
- Image grid that adapts to the number of photos (1: full-width natural aspect ratio, 2: side-by-side, 3: one full-width + two side-by-side, 4: 2×2 grid)
- Lightbox carousel — clicking any image opens a full-screen Bootstrap modal carousel starting on the clicked photo
- Linked @mentions and #hashtags pointing to their canonical x.com URLs
- Standalone pages — each tweet has its own URL at
/tweets/<tweet-id>/for direct linking - SEO-optimized — tweet pages are indexed in sitemaps with titles containing "Archived Tweet" plus @mentions and #hashtags
- Accessible — semantic
<figure>/<blockquote>/<figcaption>markup, keyboard-navigable image buttons with:focus-visible, descriptivearia-labelattributes, andprefers-reduced-motionsupport
Each archived tweet lives in content/tweets/<tweet-id>/ as a Hugo page bundle:
content/tweets/1223242916988096512/
index.md Front matter + tweet text as Markdown
photo1.jpg Attached images (if any)
photo2.jpg
Front matter:
---
title: "Archived Tweet — @mbbroberg #metrics"
date: 2020-01-31T13:53:32+00:00
tweet_id: "1223242916988096512"
author: "jflory7"
author_name: "Justin Wheeler"
categories: ["tweets"]
---Tweet body uses standard Markdown.
@mentions are linked as [@handle](https://x.com/handle) and #hashtags as [#tag](https://x.com/hashtag/tag).
{{< tweet-archive id="1223242916988096512" >}}The id parameter must match a content page's tweet_id front matter field.
The card displays the author's profile photo from the avatar front matter field.
If not set, it falls back to the site's default image (params.images[0]).
Toph uses Hugo's css.Build function to bundle modular CSS files into a single stylesheet at build time.
The source CSS is organized under assets/css/ in four directories:
assets/css/
main.css @import entrypoint (no rules, only imports)
base/ Variables, global styles, navbar, main content area
components/ Cover image, hero, PDF download, tweet archive, team card grid, post metadata, post nav, code blocks, footer
taxonomy/ Sort controls, term excerpts, categories grid, tags word cloud
blog/ Recent posts, blog archive accordion
All neutral/utility colors (text, borders, backgrounds, shadows) are defined as CSS custom properties in base/_variables.css.
Brand colors (--primary, --secondary, --accent-color) and fonts are set from Hugo config via an inline <style> block in head.html.
Both sets of variables coexist on :root without conflict.
To add new CSS for a new component, create a new _component-name.css file in the appropriate directory and add an @import line to main.css.
Toph supports quick and easy customization of colors and fonts in the Hugo config file. The site features three colors and three fonts used across all layouts:
- Colors:
- Primary: Dominant color used across all layouts in highly visible locations.
- Secondary: Background or secondary color. Most notable use in background color of all pages.
- Accent: Color to accent or emphasize content in contrast to the primary color.
- Fonts:
- Default: Used as typeface for almost all content across the site.
- Title: Used in page titles only.
- Header: Used in all headers other than the page title.
Include the following in your Hugo configuration file:
YAML:
params:
colors:
primary: darkorchid
secondary: linen
accent: darkslateblue
fonts:
default: Open Sans
title: Bungee Shade
header: Roboto SlabTOML:
[params.colors]
primary = "darkorchid"
secondary = "linen"
accent = "darkslateblue"
[params.fonts]
default = "Open Sans"
title = "Bungee Shade"
header = "Roboto Slab"Toph supports multilingual sites with built-in translations for four languages:
| Language | Code | Notes |
|---|---|---|
| English | en |
Default |
| Arabic | ar |
Right-to-left (RTL) layout |
| Hindi | hi |
|
| Spanish | es |
Translation strings are stored in i18n/{en,es,ar,hi}.yaml.
All navigation labels, footer text, 404 page messages, and UI strings are localized.
To enable multiple languages, configure the languages section in your Hugo config.
See the Hugo multilingual documentation for details.
There are a few ways to contribute to this theme.
Open a new issue to report bugs and request new features.
Fork and clone this repository. Then, open a Pull Request when you have changes to propose.
Licensed under the Mozilla Public License 2.0 (MPL-2.0). From choosealicense.com:
Permissions of this weak copyleft license are conditioned on making available source code of licensed files and modifications of those files under the same license (or in certain cases, one of the GNU licenses). Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. However, a larger work using the licensed work may be distributed under different terms and without source code for files added in the larger work.