Skip to content

Home and landing pages

Assemble a home page from one local YAML file — hero, cards, capability panels, timeline, pricing, case studies, downloads. Any page can become a landing page with the same sections.

The home page is not a template but a data file: the sections list in data/home/<language>.yaml decides which sections the page has from top to bottom, and each section’s content is looked up by name in the same file. An ordinary page with layout: landing uses the same sections.

Every section is rendered on the server. Prices, star counts, screenshots, avatars and download states all have to exist in the repository before Hugo starts; no section fetches data in the browser.

A site migrating from Docsy’s blocks/* home page has to rewrite it: the theme has no blocks/cover, blocks/section or blocks/feature shortcodes, and keeping them fails the build with template for shortcode "blocks/cover" not found. The two ways forward are the data/home/<language>.yaml described here, or layout: landing on an ordinary page.

Where the home page’s data lives

The home page’s content file keeps only a title and a description:

content/_index.md
---
title: OINK
description: A local-first, Hugo-only theme for technical documentation
---

Section data is a file per language:

home page data

  • data/
    • home/
      • en.yamlEnglish home page
      • zh.yamlChinese home page

The lookup order is data/home/<current language>.yamldata/home/en.yamldata/home.yaml for a single-language site.

The file has only two levels: a sections list, and the same-named keys that list references.

the skeleton of data/home/en.yaml
sections:
  - hero          # uses the hero: key
  - capabilities
  - type: cards   # uses the cards section, reading the release: key
    key: release
  - cta

hero: { … }
capabilities: { … }
release: { … }
cta: { … }

That is how this site’s home page is written; the complete file is data/home/en.yaml in the repository.

A minimal working home page

Paste the following, replace the text and links, and it publishes. Write internal links as site paths without a leading slash, and the theme adds the current language prefix (docs/start//docs/start/).

data/home/en.yaml
sections:
  - hero
  - cards
  - cta

hero:
  eyebrow: Local-first · Hugo only
  title_lines:
    - words:
        - { text: PGSTY OINK }
  lead: Components are written in Markdown, assets ship with the theme, and one source produces four outputs.
  image:
    light: images/hero-light.webp
    dark: images/hero-dark.webp
    alt: OINK engineering documentation illustration
  actions:
    - { label: Quick start, url: docs/start/, icon: fa-solid fa-rocket, style: primary }
    - { label: See the components, url: docs/components/, style: ghost }

cards:
  eyebrow: What it does
  title: Everything engineering documentation needs
  columns: 3
  items:
    - title: Markdown-native components
      desc: Callouts, tabs, field lists and file trees are all part of Markdown syntax.
      icon: fa-solid fa-cubes
      url: docs/components/
    - title: Four outputs
      desc: HTML, print, Markdown and RSS from one source, losing nothing.
      icon: fa-solid fa-file-export
      url: docs/customize/agents/
    - title: Local-first
      desc: Fonts, icons, search and diagram runtimes all ship with the theme; no CDN.
      icon: fa-solid fa-plug-circle-xmark
      url: docs/about/features/

cta:
  title: Start from a bilingual site that already works.
  text: Clone the documentation site, delete what you do not need, make the rest yours.
  label: Get started
  url: docs/start/
  style: primary

Hero

The hero is the first screen, and the only section with a large title and an illustration.

data/home/en.yaml
hero:
  eyebrow: OINK 0.4.0 · Local-first        # small text above the title, with a status dot
  title_lines:                             # the large title, controlled line by line
    - words:
        - { text: PGSTY OINK }
  lead: One sentence saying what this is.  # inline Markdown and <br> allowed
  note: No Node.js required                # a supplementary line with an icon
  note_icon: fa-solid fa-circle-check
  title_size: 4.25rem                      # rem / em / px only
  image:
    light: images/hero-light.webp
    dark: images/hero-dark.webp            # with only one, both modes share it
    alt: First-screen illustration
  media:
    ratio: '1fr 240px'                     # column widths for text and image
    max_width: 240px
    hide_below: md                         # hide the image below sm | md | lg | xl
  actions:
    - { label: Get started, url: docs/start/, icon: fa-solid fa-rocket, style: primary }
    - { label: GitHub, url: 'https://github.com/pgsty/oink', external: true, style: ghost }
  detail: { label: See what it looks like, url: docs/about/showcase/ }

Without title_lines it uses title, and without either the site title. The image is a CSS background: with an alt the container carries role="img", and without one it is hidden from assistive technology.

align: center gives a text-only centred first screen: the text block widens and centres, the title balances its line breaks, and note moves below the buttons. It does not accept image, and having both fails the build.

The section registry

There are 22 section types, named with hyphens (underscores in older data are normalized). Apart from the hero, each shares the three heading fields eyebrow / title / desc (or text) plus a class.

Type What it holds
hero The first screen: large title, buttons, a theme-following image
metrics Numeric facts, with optional count animation and source links
capabilities Alternating left-right capability narratives with a dedicated visual panel
principles Numbered product principles
cards A general card set: features, scenarios, entry points
logo-wall Tools and partners, as a grid or a pure-CSS marquee
gallery A wall of screenshots
testimonials Quotations with attribution
contributors People, roles, avatars and links
faq Questions and answers, collapsible or flat
markdown A stretch of free Markdown
cta The closing call to action
pricing Pricing tier cards
pricing-compare A tier-by-feature comparison matrix
command-box One copyable command
steps An ordered procedure, optionally with commands
timeline Dated milestones
code-plate Code inside a presentation panel
preview A stretch of Markdown source beside what it renders as
case-study A case: metrics plus a quotation plus a source
download One or more data/download/ records
bar-chart Numeric comparison without any chart JS

A misspelled type does not silently disappear: the build emits an unknown section type warning and skips the section. Adding --panicOnWarning in CI turns that into a build failure.

The commonest sections, minimally

Cards and capability panels are the two used most. cards controls its column count with columns:

data/home/en.yaml
cards:
  title: Use cases
  columns: 4
  link_label: Learn more
  items:
    - title: Book publishing
      meta: Long form
      icon: fa-solid fa-book-open
      desc: Numbered figures and examples, cross-references, indexes and whole-book print.
      url: docs/write/book/

capabilities is one capability per screen with a structured visual panel on the right, and visual.type must be one of shell, components, code, image or card:

data/home/en.yaml
capabilities:
  eyebrow: Value
  title: What engineering documentation needs, out of the box
  items:
    - ref: 01 / Engineering docs
      title: Built for engineers and their documentation sites
      url: docs/start/
      motto: No extra friction from the first build to long-term maintenance
      bullets:
        - 'A [deployment](docs/admin/deploy/) experience that works out of the box'
        - 'Built-in [search](docs/customize/search/) and [languages](docs/customize/i18n/)'
      value: Content teams spend their time on documentation rather than rebuilding a site.
      visual:
        type: code
        title: build.sh
        lines:
          - { class: c, prefix: '# ', text: One command, one deterministic output }
          - { class: p, prefix: '$ ', text: hugo --gc --minify }
          - { class: ok, prefix: '✓ ', text: public/ is ready to deploy }
Minimal YAML for the other ten scenario sections

These fragments come from the theme repository’s executable regression fixture tests/site/data/landing/demo/en.yaml, and the field names can be copied.

metrics:
  title: Facts
  animate: true
  items:
    - { value: 2189, compact: true, label: Stars, source: { label: Local CI data, url: 'https://example.org/' } }
    - { value: 32, suffix: '+', label: Languages }

command-box:
  title: Install
  code: hugo mod get github.com/pgsty/oink
  lang: bash
  note: The copy button comes from the on-demand landing runtime.

steps:
  title: Three steps to publish
  items:
    - { title: Clone, desc: Copy the documentation site repository. }
    - { title: Configure, desc: Change three settings., cmd: { code: hugo server } }
    - { title: Publish, desc: Push to GitHub Pages. }

timeline:
  title: Project history
  items:
    - { date: '2024', title: Prototype, desc: The first data-driven sections. }
    - { date: '2026', title: Scenario components, desc: Landing becomes a reusable shell. }

code-plate:
  title: Page configuration
  aria_label: Example configuration
  lang: yaml
  code: |
    layout: landing
    landing: pricing

preview:
  title: What you write is what you get
  file: guide.md            # the filename in the source panel header, default page.md
  source: |                 # the right side renders this Markdown with the site's own hooks
    > [!TIP] Markdown only
    > Callouts, steps and tabs are all ordinary syntax.

    1. Write Markdown
    2. Run `hugo`
    {.steps}

case-study:
  title: Migration outcome
  stats:
    - { value: 12, label: Reusable sections }
    - { value: 0, label: Remote requests }
  quote: "One YAML file replaced a bespoke page template."
  source: A site maintainer

pricing:
  title: Pricing
  tiers:
    - name: Community
      price: Free
      period: forever
      desc: The full open-source capability.
      features: [Every component, Community support]
      cta: { label: Download, url: docs/start/ }
    - name: Professional
      featured: true
      price: $3.4K
      period: /year
      features: [Priority response, Release packages]
      cta: { label: Contact us, url: 'mailto:[email protected]' }

pricing-compare:
  title: Tier comparison
  tiers: [Community, Professional]
  groups:
    - name: Support
      rows:
        - { name: Priority response, cells: [N, Y] }
        - { name: Annual fee, price_row: true, cells: [Free, $3.4K] }

download:
  title: Download
  keys: [prd5]

bar-chart:
  title: Build time
  unit: seconds
  items:
    - { label: Cold build, value: 12.3, group: cold }
    - { label: Warm cache, value: 1.6, group: warm, note: A repeat build on the same machine. }

The download section consumes exactly the data/download/<key>.yaml from Releases and downloads, introducing no second version model.

Turning any page into a landing page

Two lines of front matter make an ordinary content page a landing page: a full-width canvas that keeps the navbar, the command palette and the footer, and drops the sidebar and the outline.

content/pricing.md
---
title: Pricing
layout: landing
landing: pricing
---

The data lives in a directory parallel to the home page’s, likewise split by language:

landing page data

  • data/
    • landing/
      • pricing/
        • en.yaml
        • zh.yaml

A non-home landing page looks for its data in this order, and fails the build rather than rendering an empty page when nothing is found:

  1. sections in the page’s front matter;
  2. data/landing/<key>/<exact language>.yaml;
  3. The exact-language entry inside a single data/landing/<key>.yaml;
  4. The English or language-less record.

Small amounts of data can go in front matter, but landing: and sections: are mutually exclusive:

content/pricing.md
---
title: Pricing
layout: landing
sections:
  - type: hero
    data:
      title: Publish a product page with Hugo alone
      actions:
        - { label: Read the docs, url: docs/, style: primary }
  - type: download
    data: { title: Download, keys: [prd5] }
  - cta
---

Writing a section entry

Each item in sections is either a type-name string or a map:

Key What it does
type The section type; omitted, key is used as the type
key Which key to read data from, defaulting to the same name as type; use it to distinguish two uses of one section
data Inline data, so no top-level key is looked up
id The section’s anchor ID, generated from key / type by default
enabled: false Disables the section while keeping its data
partial Swaps in the site’s own partial. A local template convention, not portable landing data

Languages and local facts

Narrative text belongs in per-language files (zh.yaml / en.yaml). A shared record of facts can also fall back field by field: <field>_<exact language><field>_<base language><field>, with - in a language tag normalized to _. A Chinese site resolves title_zh_cn, then title_zh, then title. camelCase suffixes are not accepted.

Display text inside a section is site data, not the theme’s i18n strings. Only the theme’s own controls — marquee pause, pricing states — use translation keys. Configuring a multilingual site as a whole is in Languages.

A few optional facts on the landing shell are local too, written in hugo.yml and never fetched at runtime:

hugo.yml
params:
  offline_search: true
  ui:
    landing_search: true          # boolean; the palette appears only if the site enabled offline_search
    github_stars: 2189            # a committed number, never a GitHub API request
    alt_site: { label: 中文站, url: 'https://example.cn/' }

The footer is not home page data: it reads data/footer/<language>.yaml (or data/footer.yaml on a single-language site), and this site has one per language. A leftover footer key in data/home/<language>.yaml fails the build with a message naming the new location. How to write it is in Navigation and menus.

Output

Output What appears
HTML The full static section content, plus landing.js loaded on demand for reveal, counting, copying and theme image switching
Print Content kept; dynamic surfaces such as the marquee become a static grid, and controls are removed
Markdown Titles, prose, lists, tables and code, with no component classes
RSS Landing sections are not emitted

With JavaScript disabled the server-rendered document is still complete. The marquee’s duplicate track stays out of the accessibility tree, and pausing uses a checkbox that needs no JavaScript; with the reader’s reduced-motion preference on, movement and reveal are switched off.

Verify

  1. The build is warning-free: hugo --printPathWarnings --panicOnWarning. A misspelled type, a missing data key, and landing alongside sections all surface here.
  2. Open the home page and any landing page, compare each section against the data file, and look at every language.
  3. Reload with JavaScript disabled: the content is still there, only without motion.
  4. Look at both light and dark, confirming image.light and image.dark are each correct.
  5. When deploying to a subpath, confirm internal links and images all carry the prefix.