GOHTML / Go Template Coding Standards#
Scope. This standard governs formatting and readability for all Go
html/templatefiles (.gohtml) in the Lesstruct default theme and in user-authored theme overrides. It applies tointernal/api/template/pages/and to any.gohtmlfile under aTHEME_DIR/templates/directory.
Core Rules#
1. Indentation#
Use tabs — one per nesting level — to reflect logical / HTML nesting. This
matches gofmt, the WordPress HTML standard, and Hugo defaults.
| |
2. Whitespace Trimming#
Use {{- and -}} around block-control actions that span lines so
indentation does not leak into rendered output:
{{- define "name"}}/{{- end}}{{- range ...}}/{{- end}}{{- if ...}}/{{- else if ...}}/{{- else}}/{{- end}}{{- with ...}}/{{- end}}{{- template "name" ...}}
| |
| |
3. Inline Runs Stay on One Line#
When an action sits inside an inline sequence (nav links, author byline, language links, tags), keep the whole run on one line. Do not break it across lines unless you intend to change the rendered spacing.
| |
| |
Rule of thumb: if two elements are both display: inline by default (e.g.
<img>, <a>, <span>, <time>, <strong>), a newline between them
renders as a visible space. A newline between two block elements (e.g.
<div>, <section>, <article>, <header>, <footer>, <main>, <nav>,
<form>) is insignificant.
4. Inline Conditionals#
Keep single-element conditionals on one line — this reads cleanly and avoids inline whitespace changes:
| |
5. Attribute Style#
- Always double-quote attribute values.
- Use lowercase tag and attribute names.
- Preserve
style="display:none"verbatim — test assertions check for it exactly. - Keep the existing void-element style (no self-closing slash):
<img ...>not<img ... />. Changing this is a separate decision.
6. Block-Level vs Inline Conditionals#
Block-level conditionals wrap entire elements and get trimmed + indented:
| |
Inline conditionals that wrap or precede a single inline element stay on one line with the element:
| |
7. define and end#
{{- define "name"}}starts at column 0.{{- end}}closes at column 0.{{template "body" .}}is indented inside its parent, with no trimming (the layout owns the{{- define}}/{{- end}}pair).
8. Comments#
Do not add comments in markup unless documenting a non-obvious data contract. If you do add a comment, use HTML comments:
| |
Before / After Example#
Before (flat, compact, hard to read):
| |
After (indented, trimmed, readable):
| |
Verification#
After reformatting any .gohtml file:
- Run
go test ./internal/api/template/... -v— all substring assertions must still pass (especiallystyle="display:none",href="/static/style.<hash>.css",<p>Hello <strong>world</strong></p>). - Visually inspect rendered HTML output via
httptestto confirm whitespace changes are restricted to block contexts; no new visible-space regressions in inline runs. - Run
make lint && make testat the end of the change.
Why This Matters#
Lesstruct ships server-rendered HTML unminified — only base.css and style.css are
minified. This means every tab and newline in a .gohtml file is sent to the
browser. Without trimming, reformatting for readability would inject blank lines
and extra whitespace into rendered output. Strategic {{- -}} trimming
lets us indent for humans without changing the output for browsers.
The standard applies to both the default theme (embedded at
internal/api/template/pages/) and user-authored theme overrides
(themes/<name>/templates/), since both are parsed by the same Go template
engine and rendered unminified.