Skip to content
New: Customize your theme with an AI assistant. Read the guide

How Homio Works

Skill level: Everyone · Time: 10 minutes

Read this page once and the rest of the theme stops being a mystery. Homio is built from a few simple ideas that repeat everywhere.

The Big Picture

Homio is a static site: when you run npm run build, it turns your text files into finished HTML pages in dist/. There is no database and no admin panel. Your files are the website.

config.toml ──► site settings, [business] details, forms, estimator, booking
menu.en.json ─► header and footer links
theme.css ────► colors, type scale, breakpoints
pricing/ ─────► every price: estimator, booking, plans, add-ons, "from" prices
ROUTE src/pages/[...lang]/pricing.astro
│ which sections, in which order
│
├─ PAGE src/content/pricing/english/-index.md
│ title, SEO, page header, named blocks
│
└─ each SECTION
├─ default: sections/english/plans-section.md
├─ + page block: plansSection: { ... }
└─ look: options.layout (many sections)
Base.astro wraps every page:
head · header · page · call to action · footer · mobile action bar · cookie banner

1. Routes Decide What A Page Shows

Each file in src/pages/[...lang]/ is one page (a route). It lists the sections that page shows, top to bottom. This is the pricing page, src/pages/[...lang]/pricing.astro, shortened:

<Base {...entry?.data}>
<PageHeader content={{ ...entry?.data, currentLabel: entry?.data.title }} />
<JumpNav items={jumpNav} />
<LayoutVariants section="plans" id="plans" content={plansSection} />
<EstimateSection id="estimate" content={estimateSection} />
<AddOnsSection id="add-ons" content={addOnsSection} />
<IncludedSection id="compare" content={includedSection} />
<LayoutVariants section="faq" id="policies" content={questionsSection} />
</Base>

To reorder sections, move a line. To remove a section from one page, delete its line. The [...lang] folder name means one file builds every language: English at /pricing/, French at /fr/pricing/.

Sections that have more than one layout are drawn through <LayoutVariants section="..." content={...} />. Keep that wrapper when you move them.

2. Sections Have Default Content

Every section has a matching content file in src/content/sections/<language>/. For example, EstimateSection.astro reads estimate-section.md. That file holds the section’s default text, lists, buttons, and options, with comments that explain them.

Edit a section file and the change appears everywhere that section is used, unless a page overrides it. Set enable: false at the top of the file to hide the section on every page.

3. Pages Can Override Any Section

A page file can change a section for that page only with a named block. Every page file in Homio uses them, the homepages included. This is the start of the hero block on the main homepage:

src/content/homepage/english/-index.md
---
heroSection: # defaults: hero-section.md
enable: true
title: "A cleaner home, the same crew every visit"
checks: ["Insured", "Background-checked", "Fixed prices"]
options:
layout: "default" # default | split | form | band
---

Block names are the section name in camelCase: heroSection, plansSection, questionsSection, ctaSection, and so on. Most blocks have a comment that names the section file they override (# defaults: hero-section.md). Sections has the full list.

A block with a wrong name, for example faqSection: instead of questionsSection:, is ignored without an error. Copy block names from the page file, the Sections table, or sectionsSchema in src/sections.schema.ts.

4. How Overrides Merge

When a page block meets the section’s default content, Homio merges them with four rules:

You write in the page blockResult
A text or number (title: "Hello")Replaces the default value
An object (button:, options:)Merges key by key: only the keys you write change
A list (checks:, steps:, list:)Replaces the whole list. Write every item you want to keep
An empty string (description: "")Clears the value. Leave the key out to keep the default

So this block switches the call to action to another layout on one page and keeps all its text and buttons:

ctaSection:
enable: true
options:
layout: "split"

Keep enable: true at the top of each page block, as the shipped files do. In page files that have a schema, some blocks (the call to action, the contact section, the logo strip) count as hidden when enable is left out.

5. Layouts Change The Look, Not The Content

Many sections can be drawn in more than one way. The layout is one option:

options:
layout: "split" # the hero: default | split | form | band

The allowed values are always in the comment next to layout:. Most content fields stay the same, so you can switch back and forth. A few fields belong to one layout only, and their comment says so, for example the hero’s form (form layout) or the call to action’s ticket (split layout). Set the layout in the section file (every page) or in a page block (one page).

Open /elements/ on your local site to see the layouts of the multi-layout sections and of the page header side by side. See Section Layouts for every value.

6. Base Wraps Every Page

src/layouts/Base.astro adds the parts that every page shares:

  • the <head>: title, description, Open Graph tags, structured data, fonts, and your [head] scripts
  • the header in one of four layouts ([settings.layout] header in config.toml, or headerLayout in a page), with the menus and the phone number from [business]
  • the call to action above the footer, from call-to-action.md and the page’s ctaSection block
  • the footer in one of four layouts ([settings.layout] footer, or footerLayout in a page), from the menu files, [business], src/config/social.json, and src/i18n/<language>.json
  • the Call / Get a price bar on phones ([settings.mobileActionBar]), the cookie banner, the back-to-top button, and shared scripts

7. Business Details Are Set Once

Your company name, phone, email, address, opening hours, time zone, and closed dates live in [business] in config.toml. The header, footer, contact page, area pages, booking form, structured data, and llms.txt all read them from there.

Inside Markdown text and some content fields, you can write {name}, {phone}, {email}, {address}, or {hours}, and Homio fills in the value from [business]. The tokens work in .md files, not in .mdx files. Field comments say where a token works, for example # Inline markdown and {phone} work in announcement-bar.md.

8. Prices Come From One File

The estimator, the booking form, the plan cards, the add-ons, the trade rate sheet, and the “from” prices on service cards all compute their numbers from src/content/pricing/<language>/-index.md: visit frequencies with their discounts, one rate card per service, and the add-ons. You never type a price into a section. Change a rate there and every place that shows it updates. See Pricing & Estimator.

9. Small Interface Text Lives In i18n Files

Labels that are not page content, such as the header button (“Get a price”), form messages, the footer description and copyright, and the 404 text, live in src/i18n/en.json and src/i18n/fr.json. Several config.toml labels are empty on purpose (label = ""): empty means “use the translated text from the i18n file”. Fill them in only if your site has one language.

10. Config Is Converted Before Astro Reads It

Astro cannot read TOML, so npm run dev, npm run build, and npm run astro-check first convert src/config/config.toml into .astro/config.generated.json. While npm run dev is running, this happens every time you save config.toml, and the page reloads. Always edit the TOML file, never the generated JSON.

Restart npm run dev after you change trailingSlash or [settings.multilingual], after you add a language, and after you change src/config/fonts.json: Astro reads those only when it starts.

11. Collections Hold The Repeated Content

A collection is a folder of similar entries, each one a file. Homio’s collections are services, pricing, areas, industries, projects, reviews, team, careers, blog, author, pages, homepage, about-us, contact, faq, guarantee, and sections. They are defined in src/content.config.ts, and most have a schema: the list of fields an entry may have. See Collections.

12. What Happens During A Build

  1. config.toml is converted to JSON.
  2. Astro checks the content collections that have a schema, including the named blocks in their page frontmatter (sectionsSchema in src/sections.schema.ts). A wrong value there stops the build with the file name and field. The sections and contact collections have no schema, so a wrong value in those files is not reported. Check those pages in the browser.
  3. Every route is rendered once per language into dist/. A page without its own image gets a generated Open Graph card.
  4. llms.txt, llms-full.txt, the Markdown copies of pages, the sitemap, robots.txt, and the RSS feed are written.
  5. Yellow warnings print for anything still a placeholder: [site] for the https://example.com site URL, [forms] for a placeholder form address, [business] for a value it could not read, and [pricing] when the English and French price files differ.
  6. Draft pages are removed from the sitemap.

Development vs. Production

Some things behave differently while you work with npm run dev:

Featurenpm run devBuilt site (npm run build)
Pages with draft: trueShown, so you can preview themNot built
Changes to config.tomlShown when you save. Restart after changing languages, trailingSlash, or fontsRead at the start of every build
Placeholder form addressA warning box inside the form, and a [forms] line in the terminalA [forms] line in the build output
Site URL warningNot shownPrinted when the Astro build finishes, while the URL is example.com
Open Graph cards for each pageNot generatedGenerated during the build
Forms and booking requestsSent to the address in config.toml, so test with careSame

Find Where Any Text Comes From

See a sentence on the site and want to change it? Search the whole project for it: in VS Code press Ctrl + Shift + F (⌘ + Shift + F on Mac) and paste a few words. It will be in one of these places, checked in this order:

  1. The page’s own file, in a named block (src/content/<page>/<language>/-index.md, or src/content/pages/<language>/ for single pages such as /book/)
  2. The section’s default file (src/content/sections/<language>/)
  3. A collection entry (src/content/services/, areas/, projects/, reviews/, team/, careers/, blog/)
  4. The price file (src/content/pricing/<language>/-index.md) for prices, plan names, frequencies, and add-ons
  5. The interface text (src/i18n/<language>.json)
  6. The menus (src/config/menu.<language>.json)
  7. Site settings and business details (src/config/config.toml)

If the same text appears in both a page block and a section file, the page block wins on that page. If you can’t find a phone number, an address, or opening hours anywhere in src/content/, it comes from [business] in config.toml.

Type something to search...