A minimalist, reading-first Hugo theme optimized for both humans and AI agents.
ReaderFirst strips away every decoration that gets between the reader and the text. It ships with a clean typographic system, an automatic dark mode that follows the OS, an optional collapsible table of contents for long posts, and structured data (JSON-LD + JSON Feed) so your content is easy for both search engines and AI assistants to consume.
- Minimalist — One reading column, generous whitespace, and a restrained type scale. Nothing competes with the prose.
- Adaptive dark mode — Follows the visitor's
prefers-color-schemesetting automatically, with no flash of incorrect theme on load. - AI-friendly — Emits JSON-LD structured data (Article, BlogPosting, publisher) and a JSON Feed alongside the standard HTML and RSS outputs, so AI agents and search engines can parse your site reliably.
- Table of contents — An optional outline generated from a post's headings. Opt in with
toc = true; collapsible on small screens, sticky sidebar with live reading progress on wide viewports. - Editorial shortcodes —
pullquoteandasidefor magazine-style emphasis without leaving Markdown. - Article chrome — Optional subtitle/dek, category kicker, copy-link button, author card, richer prev/next and related summaries.
- Heading anchors — Every content heading gets a stable
idand a hover-revealed#permalink for easy deep-linking, via a Markdown render hook. - Code copy & language badge — Every fenced code block gets a one-click copy button (vanilla JS, with
aria-liveconfirmation) and a language label badge. - Image lightbox — Click an article image to view it full-screen; Esc or click to close. Vanilla JS, no dependencies, no-op when a post has no images.
- Breadcrumbs — A minimal text trail (Home / Section / Title) above each post orientates the reader.
- Multilingual & CJK — Ships with English and Chinese translations; CJK content gets looser line-height, hanging punctuation, and automatic Han–Latin spacing. RTL languages are mirrored via logical CSS properties.
- JSON Feed — A
index.jsonfeed is published at the site root so feed readers and agents have a machine-readable subscription endpoint. - Accessible — Semantic HTML, sensible heading order, and tested color contrast in both themes.
- Fast — No client-side JavaScript framework, no layout shift, just static HTML and a small CSS payload.
To keep the theme light and reading-focused, the following are not bundled. Most can be added via the optional layouts/partials/head/custom.html hook or a user-supplied assets/js/main.js:
- Comments — no Disqus/Giscus/Remark42 wired in. Drop a comments partial into your site if needed.
- Search — no client-side search library. The JSON Feed at
/index.jsoncan serve as a search index for external tooling. - Analytics — no GA/Plausible/etc. Inject your snippet via
partials/head/custom.html. - Client-side JS framework — none, by design. The only inline scripts are a no-flash theme resolver, the theme toggle, optional TOC state/progress, copy-link, a per-post code-copy button, and an image lightbox; all vanilla, dependency-free.
Initialize modules in your site, then import ReaderFirst:
cd your-site
hugo mod init github.com/yourname/yoursiteAdd to your site's hugo.toml:
theme = 'github.com/codertesla/ReaderFirst'
[module]
[[module.imports]]
path = 'github.com/codertesla/ReaderFirst'Fetch the theme:
hugo mod get github.com/codertesla/ReaderFirst@v0.2.0
hugo mod tidyTo update later:
hugo mod get -u github.com/codertesla/ReaderFirst
hugo mod tidyAdd ReaderFirst to your existing Hugo site as a submodule:
cd your-site
git submodule add https://github.com/codertesla/ReaderFirst.git themes/ReaderFirst
git submodule update --init --recursiveThen enable it in your site's hugo.toml:
theme = 'ReaderFirst'To update the theme later:
git submodule update --remote themes/ReaderFirstIf you prefer not to use modules or submodules:
cd your-site
git clone https://github.com/codertesla/ReaderFirst.git themes/ReaderFirstA minimal hugo.toml for a site using ReaderFirst:
baseURL = 'https://example.com/'
title = 'My Site'
theme = 'github.com/codertesla/ReaderFirst'
[module]
[[module.imports]]
path = 'github.com/codertesla/ReaderFirst'
[params]
description = 'A minimalist, reading-first blog.'
author = 'Your Name'
mainSections = ['posts']
[taxonomies]
tag = 'tags'
category = 'categories'
[outputs]
home = ['html', 'rss', 'json']
[outputFormats.JSON]
mediaType = 'application/json'
baseName = 'index'
[markup]
[markup.highlight]
# Token colors are defined (theme-aware) in the theme's CSS, so no `style`.
noClasses = false
lineNos = false
tabWidth = 2All fields are optional. Per-post front matter (TOML shown; YAML works too):
| Field | Type | Effect |
|---|---|---|
title |
string | Post title (heading, <title>, Open Graph, JSON-LD). |
subtitle |
string | Optional dek under the title (falls back to description). |
description |
string | Meta description; also used as dek when subtitle is absent. |
date |
date | Publish date; drives ordering, meta and reading time. |
lastmod |
date | Shows an "Updated" badge when it differs from date. |
draft |
bool | true hides the post from builds and emits noindex. |
author |
string | Byline; falls back to params.author. Used in meta, author card, and JSON-LD. |
authorBio |
string | Optional bio on the author card (falls back to params.authorBio). |
tags |
[]string | Tag chips, keywords meta, JSON-LD keywords, and related-post matching. |
toc |
bool | true renders the outline (sticky rail on wide screens, collapsible on small). |
cover |
string | Path (or page-bundle cover.*/featured.*) used as the Open Graph / Twitter share image. |
license |
string | Adds a license field to the post's BlogPosting JSON-LD. |
+++
title = 'My Post'
subtitle = 'A short dek that sets the reading expectation.'
date = 2026-07-01
tags = ['hugo', 'markdown']
toc = true
+++Shortcodes inside Markdown:
{{</* pullquote attr="optional attribution" */>}}
A sentence worth lifting out of the flow.
{{</* /pullquote */>}}
{{</* aside label="Note" */>}}
A short editorial aside beside the main argument.
{{</* /aside */>}}The post template shows up to three related posts (server-side, zero JS) based on shared tags. Enable it by configuring Hugo's related-content index:
[related]
includeNewer = true
threshold = 80
toLower = true
[[related.indices]]
name = 'tags'
weight = 100
[[related.indices]]
name = 'date'
weight = 10[params]
description = 'A minimalist, reading-first blog.'
author = 'Your Name'
# authorBio = 'One line about the author for the end-of-post card.'
# paginateSize = 10 # posts per page on home / section / term lists
# publisherType = 'Person' # 'Person' (personal blog) or 'Organization' (default). When 'Person', the JSON-LD publisher uses the author name and omits the logo.
# logo = 'images/logo.png' # populates publisher.logo in JSON-LD (Organization only)
# default_og_image = 'images/og.png' # fallback social share image
# twitter = '@yourhandle' # twitter:site meta
# [params.style] # optional design tokens (omit for defaults)
# bodyFont = 'Georgia, serif'
# headingFont = 'Georgia, serif'
# accent = '#7a2e2e'
# maxWidth = '720px'
# measure = '72ch'The exampleSite/ directory contains a ready-to-run demo. From the theme repository root:
cd exampleSite
hugo mod tidy
hugo serverIt imports the theme via Hugo Modules (go.mod uses a local replace so no GitHub fetch is needed during development).
Hugo Themes requires two preview images in the images/ folder at the repository root:
images/screenshot.png— 1500×1000 (3:2) full-screen capture of the theme.images/tn.png— 900×600 (3:2) thumbnail used in the themes gallery.
These images are located in the images/ folder and will be used automatically by the Hugo Themes gallery.
ReaderFirst is released under the MIT License.
