How Kivo Works
Skill level: Everyone · Time: 10 minutes
Read this page once and the rest of the theme stops being a mystery. Kivo is built from a few simple ideas that repeat everywhere.
The Big Picture
Kivo 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: name, URL, formsmenu.en.json ─► header and footer linkstheme.css ────► colors, type scale, breakpoints
ROUTE src/pages/[...lang]/pricing.astro │ which sections, in which order │ ├─ PAGE src/content/pricing/english/-index.md │ title, SEO, named blocks │ └─ each SECTION ├─ default: sections/english/plans-section.md ├─ + page block: plansSection: { ... } └─ look: options.layout
Base.astro wraps every page: head · header · page · CTA · footer · cookies1. 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 homepage, src/pages/[...lang]/index.astro, shortened:
<Base {...homepageContent?.data} className="framed"> <LayoutVariants section="hero" content={heroSaas} /> <Clients content={clientsSection} /> <LayoutVariants section="steps" content={stepsSection} /> <StackSection content={stackSection} /> <!-- ...more sections... --></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/.
2. Sections Have Default Content
Every section has a matching content file in src/content/sections/<language>/. For example, PlansSection.astro reads plans-section.md. That file holds the section’s default text, lists, buttons, and options, with comments that explain each one.
Edit a section file and the change appears everywhere that section is used. 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. The pricing page uses this to keep the default plan cards:
---title: "Pricing"plansSection: # overrides src/content/sections/english/plans-section.md options: layout: "default"---Block names are the section name in camelCase: heroSaas, plansSection, questionsSection, ctaSection, and so on. Each page file lists the blocks it supports in its comments. Sections has the full list.
A block with a wrong name, for example teamSection: instead of teamGridSection:, is ignored without an error. Copy block names from the page file’s comments, the Sections table, or sectionsSchema in src/sections.schema.ts.
4. How Overrides Merge
When a page block meets the section’s default content, Kivo merges them with four rules:
| You write in the page block | Result |
|---|---|
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 (plans:, list:, inputs:) | Replaces the whole list. Write every item you want to keep |
An empty string (title: "") | Clears the value. Leave the key out to keep the default |
So this block changes only the button label and keeps its URL, icon, and style:
heroSaas: primaryButton: label: "Start your trial"5. Layouts Change The Look, Not The Content
14 sections can be drawn in more than one way (the page header too). The layout is one option:
options: layout: "centered" # the hero: default | centered | screenshot | code | formThe content fields stay the same, so you can switch back and forth freely. Set the layout in the section file (every page) or in a page block (one page). Two tools help you choose:
- The layout gallery at
/elements/shows every layout of these sections and of the page header on one page (the contact section’ssaleslayout is shown on/contact/). - The Change layout button on the homepages previews every layout of the section you are looking at, and shows the line to copy. It is a demo feature and is off by default: turn it on with
[settings.layoutSwitcher] enable = truewhile you choose, and off again before you launch.
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 announcement bar and the header, from
[settings.layout] headerand the menu files - the call to action above the footer, from
src/content/sections/<language>/call-to-action.md. A page changes or hides it with actaSection:block (enable: falsehides it) - the footer, from
[settings.layout] footer, the menu files, andsrc/i18n/<language>.json - the cookie banner, the back-to-top button, and shared scripts
7. Small Interface Text Lives In i18n Files
Labels that are not page content, such as “Log in”, “Subscribe”, “Copy”, or search messages, live in src/i18n/en.json and src/i18n/fr.json. Many 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.
8. Config Is Converted Before Astro Reads It
Astro cannot read TOML, so npm run dev and npm run build first convert src/config/config.toml into .astro/config.generated.json. While npm run dev is running, this happens every time you save. Always edit the TOML file, never the generated JSON.
Restart npm run dev after changing trailingSlash or anything under [settings.multilingual].
9. What Happens During A Build
config.tomlis converted to JSON.- Astro checks the content collections that have a schema in
src/content.config.ts, including the named blocks in their page frontmatter (sectionsSchemainsrc/sections.schema.ts). A wrong value there stops the build with the file name and field. Thesections,contact,faq, andpricingcollections have no schema, so a wrong value in those files, such as a misspelledlayout, is not reported. Check those pages in the browser. - Every route is rendered once per language into
dist/. - Open Graph images are drawn for pages without an
image, plusllms.txt,llms-full.txt, the Markdown copies of pages, the sitemap,robots.txt, and the RSS feeds. - If the site URL is still the
https://example.complaceholder, a yellow[site]warning prints when the Astro build finishes, just before thepage(s) builtline. - Pagefind builds the search index from the finished pages.
- Draft pages are removed from the sitemap.
Development vs. Production
Some things behave differently while you work with npm run dev:
| Feature | npm run dev | Built site (npm run build) |
|---|---|---|
Pages with draft: true | Shown, so you can preview them | Not built |
| Site search | Shows “search unavailable” | Works |
| Form endpoint warnings | Shown inside forms with placeholder URLs, and in the terminal | Printed in the terminal only; visitors never see them |
| Site URL warning | Not shown | Printed after the Astro build step (before the search index output) while the URL is example.com |
| Open Graph images | Not drawn | Drawn for every page without an image |
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:
- The page’s own file, in a named block (
src/content/<page>/<language>/-index.md) - The section’s default file (
src/content/sections/<language>/) - The interface text (
src/i18n/<language>.json) - The menus (
src/config/menu.<language>.json) - Site settings (
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.