Skip to content
thomaslaurensonPublic

About

Blazingly fast, highly opinionated flyweight Hugo theme

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

85 Commits

Folders and files

Repository files navigation

letsblaze

Blazingly fast, highly opinionated flyweight Hugo theme

Philosophy

letsblaze is built around one principle: the fastest resource is one that was never requested.

  • ⛔ No JavaScript
  • 🔗 No CSS
  • ✏️ No web fonts
  • ☁️ No CDN calls
  • 🚀 Plain HTML with a single inline <style>

The hard rule

The constraint that never bends is about the network: no JavaScript, no external requests, fast. Every page is self-contained HTML with one inline <style> block. Nothing else ships to the browser. The R-series constraints below enforce this and are non-negotiable.

Where the CSS line is drawn

"Minimal CSS" is not the rule. It was once used as a proxy for the hard rule above, and that proxy is misleading. Because the CSS is already inline, one more selector costs a few dozen bytes inside an already-loaded document. It triggers no request and no render-blocking. So the amount of CSS is the wrong thing to police.

The right question for any rule is: does it earn its bytes by serving reading or navigation? A CSS rule is allowed if it does one of three things:

  1. Prevents a usability failure: unreadable line length, invisible focus state, or content that can't be navigated to

  2. Communicates structure: show "where am I" from "where can I go," or separating navigation from content

  3. Respects user or OS intent: dark mode, reduced motion, system fonts

A CSS rule is rejected if it only decorates: gradients, drop shadows, brand accent colours, rounded corners, hover animations, or anything whose only loss, if removed, is that the page looks less styled.

Features

  • No JavaScript: no <script> tags of any kind; <details> and CSS do the interactive work
  • No external requests: no linked stylesheets, web fonts, or CDN calls, just one inline <style> per page
  • Dark mode: follows the OS prefers-color-scheme setting, with no toggle, cookie, or flash
  • Blog and docs: a paginated blog with tags and per-tag feeds, plus a nested docs section with breadcrumbs
  • Math: LaTeX renders to native MathML at build time, so no KaTeX or MathJax ships to the reader
  • Syntax highlighting: Chroma highlights code at build time with inline styles, in a monochrome palette
  • Images: embed, link-same-tab, and link-new-tab modes, with <figure> captions and LCP-aware loading
  • Accessible: skip link, labelled landmarks, aria-current, and semantic HTML throughout
  • SEO: canonical URLs, Open Graph tags, Schema.org microdata, RSS autodiscovery, and a sitemap
  • Shortcodes: sub, sup, mark, and abbr

See the theme running at letsblaze.thomaslaurenson.com, which doubles as the documentation: Installation, Configuration, Features, and Markdown.

Installation

Requirements

  • Hugo 0.146.0 or later

Option 1: Git submodule (recommended)

Add letsblaze as a git submodule:

git submodule add https://github.com/thomaslaurenson/letsblaze themes/letsblaze

Set the theme in your hugo.toml:

theme = "letsblaze"

To update the theme later:

git submodule update --remote themes/letsblaze

Option 2: Manual clone

This method is beginner-friendly and has no Git dependency management.

git clone https://github.com/thomaslaurenson/letsblaze themes/letsblaze

Set the theme in your hugo.toml:

theme = "letsblaze"

To update, delete the folder and clone again, or git pull inside it.

Option 3: Hugo Modules

Requires Go to be installed. Your site must be a Hugo module:

hugo mod init github.com/<you>/<your-site>

Set the theme in your hugo.toml, using the full module path:

theme = "github.com/thomaslaurenson/letsblaze"

Hugo downloads it on the next build. To update later:

hugo mod get -u github.com/thomaslaurenson/letsblaze

Constraints

Every design decision is governed by a numbered constraint. These identifiers are used in scripts/test.sh so test failures trace directly to this document.

Constraints are grouped by category with a category prefix:

  • R: Resources & CSS authoring
  • C: CSS integrity
  • S: Semantic HTML
  • M: SEO & metadata

Resources & CSS authoring

ID Constraint
R1 No JavaScript: no <script> tags of any kind
R2 No external CSS: no rel="stylesheet" links
R3 No CDN resources: no cdn., fonts.googleapis, or fonts.gstatic URLs
R4 No inline style=: no style= attributes on HTML elements (Chroma <span> and <pre> are exempt). Code fence line numbers are ignored because Chroma renders them as a <table> with inline styles; hl_lines is honoured. Hugo's default table output aligns cells with style="text-align", so the theme's table render hook writes data-align attributes instead (see C17).
R5 No CSS frameworks or utility classes: no Tailwind/Bootstrap/etc., no atomic or utility classes (e.g. mt-4, flex), and no class used purely for decoration. Semantic classes that name a structural region (e.g. docs-sidebar, breadcrumb) are permitted, because they enable structure-communicating CSS that is already inline and costs no request. Chroma and Goldmark footnote classes remain exempt.

CSS integrity

All CSS is inline inside a <style> block in <head>, in layouts/_partials/head-styles.html. Every rule has an explicit justification.

New rules must pass the gate in Philosophy: they prevent a usability failure, communicate structure, or respect user/OS intent. Decorative rules are rejected. When adding a constraint here, give it the next C number and a one-line justification, then add a matching check in scripts/test.sh.

ID Constraint Justification
C1 CSS inline in <head> No linked file = no extra HTTP request, no render blocking, no FOUC
C2 Skip link hidden off-screen position: absolute; left: -9999px, revealed on :focus with z-index: 1, background, and padding to ensure visibility
C3 body { max-width: 100ch; margin: 0 auto; padding: 1rem } Prevents unreadable line lengths on wide viewports; the auto margin centres the column and the padding keeps text off the viewport edge on narrow screens
C4 body { line-height: 1.6 } Browser default is too tight for comfortable reading
C5 img { max-width: 100%; height: auto } Responsive images; height: auto prevents CLS alongside explicit width/height attributes
C6 table { border-collapse: collapse } .table-wrap { overflow-x: auto } on the wrapper emitted by the table render hook confines horizontal scroll to the table itself, so the page never scrolls sideways. A wrapper is used rather than display: block on the table because changing a table's display strips its semantics in some browsers, notably Safari
C7 nav ul { list-style: none; margin: 0; padding: 0 } Removes browser bullet and indent defaults from all nav lists; the breadcrumb <ol> gets the same reset
C8 [aria-current="page"] { font-weight: bold } Active-link indicator without a class
C9 Dark mode via prefers-color-scheme: dark Follows OS preference (no JavaScript, no toggle, no cookie). The block restyles the body, links, inline code, <mark>, table borders and the skip link so each keeps readable contrast on the dark background; fenced code keeps Chroma's own inline colours
C10 pre { overflow-x: auto } Wide code blocks scroll horizontally instead of being clipped
C11 body { font-size: 18px } Browser default (16px) is too small for comfortable long-form reading
C12 Retired Post lists are <ul> elements, so the former article + article spacing rule matched nothing and was removed. The number is kept so older references still resolve
C13 math[display="block"] { overflow-x: auto } Wide display equations scroll horizontally within their own box instead of overflowing the page, mirroring C6 (tables) and C10 (code)
C14 li { display: inline } in the header, breadcrumb and tag navs Lays navigation out on one line so it reads as a strip separate from the content, instead of a vertical list that pushes the page down; the list markup stays so assistive technology still announces item counts
C15 :root { color-scheme: light dark } Tells the browser both schemes are supported, so scrollbars, form controls and the <details> marker follow the OS preference instead of staying light; C9 only restyles the theme's own elements
C16 th, td { border: 1px solid; padding: 0.4rem 0.8rem } Cell borders and padding keep tabular data readable; without them columns run together and rows cannot be followed across
C17 [data-align] { text-align } Honours the column alignment the author wrote in Markdown. Hugo's default table output does this with style="text-align", which R4 forbids, so layouts/_markup/render-table.html emits data-align attributes instead
C18 li + li::before { content: " / " / "" } in the header and breadcrumb navs Separates inline nav items so they do not run together. The alternative-text form hides the glyph from screen readers, which would otherwise announce it
C19 .post-meta { display: grid } Lays the blog post date, tags and author out as label and value columns, so the block reads as metadata rather than as body text and stays compact; .post-meta dt is bold to mark the labels

Semantic HTML and accessibility

ID Constraint
S1 Skip link: <a href="#main-content">Skip to content</a> on every page
S2 aria-label on every <nav>
S3 aria-current="page" on the active nav link
S4 Site title as bare <a> on every page, reserves <h1> for page content. Optionally replaced by a custom logo partial (see Logo).
S5 <time datetime="..."> on blog post dates
S6 Image rendering controlled by imageMode param: three modes: embed (default): wraps a standalone image in <figure> (see Images) and renders any other image as a bare <img>, first image on page uses loading="eager" fetchpriority="high", subsequent images use loading="lazy"; link-same-tab: renders a bare <a> link using alt text; link-new-tab: same with target="_blank" rel="noopener noreferrer". Overridable per-page in front matter.
S7 Breadcrumb navigation: <nav aria-label="Breadcrumb"> with <ol> on every blog post page and every docs page below the docs root, which has no ancestors to show; breadcrumb walks .Ancestors so arbitrary nesting depth is supported.

SEO and metadata

ID Constraint
M1 <meta charset> and viewport on every page
M2 Canonical URL: <link rel="canonical"> on every page
M3 Meta description: falls back through page description, summary, then site description
M4 Open Graph tags: og:title, og:description, og:type, og:url on every page
M5 og:site_name on every page
M6 Schema.org microdata: blog posts carry itemscope itemtype="...BlogPosting" (no <script> required)
M7 article:published_time and article:modified_time on blog posts
M8 RSS autodiscovery: <link rel="alternate" type="application/rss+xml"> in <head> on pages with feeds
M9 noindex in <head> on the 404 page
M10 <meta name="author"> on every page

About

Blazingly fast, highly opinionated flyweight Hugo theme

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages