Astro Layouts
Template inheritance — wrap pages in shared structure without repeating yourself.
On this page
Layouts Are Components That Wrap Pages
Layouts are a specific pattern on top of components. Once you have layouts down, move on to content collections and styling. For the wider context start at the mental model.
A layout is just a component that provides the shared structure (html tag, head, header, footer) and uses <slot /> for page-specific content.
LaTeX: A documentclass defines margins, fonts, header/footer. Your content goes in \begin{document}...\end{document}.
Astro: A layout defines <html>, <head>, header, footer. Your content goes in <slot />.
\documentclass{article} ← Layout\begin{document} Your content here ← <slot />\end{document}A Minimal Layout
---interface Props { title: string;}const { title } = Astro.props;---
<html lang="en"> <head> <meta charset="utf-8" /> <title>{title}</title> </head> <body> <header>krowdev</header> <main> <slot /> <!-- page content injected here --> </main> <footer>© 2026</footer> </body></html>Using it:
---import Base from '../layouts/Base.astro';---
<Base title="About"> <h1>About</h1> <p>This replaces <slot /> in Base.astro</p></Base>Layout Composition (Extending)
Layouts can wrap other layouts. krowdev uses a two-layer system:
Base.astro provides the html shell, header, a content slot, and footer. KBEntry wraps Base, adding a three-column grid and its own slot for article markdown.
graph TD
accTitle: krowdev's two-layer layout system
accDescr: Base.astro provides the html shell, header, a content slot, and footer. KBEntry wraps Base, adding a three-column grid and its own slot for article markdown.
base["Base.astro"]
base --> head1["<html>, <head>, meta tags"]
base --> header["Header (nav, logo, theme toggle)"]
base --> bslot["<slot /> — KBEntry fills this"]
base --> footer["Footer"]
kb["KBEntry.astro (uses Base)"]
kb --> callbase["Calls <Base title={title}>"]
kb --> grid["3-column grid: sidebar | content | ToC"]
kb --> kbslot["<slot /> — article markdown fills this"]
Base.astro
├── <html>, <head>, meta tags
├── Header (nav, logo, theme toggle)
├── <slot /> ← KBEntry.astro fills this
└── Footer
KBEntry.astro (uses Base)
├── Calls <Base title={title}>
├── 3-column grid: sidebar | content | ToC
└── <slot /> ← actual article markdown fills this
---import Base from './Base.astro';import SeriesSidebar from '../components/SeriesSidebar.astro';import TableOfContents from '../components/TableOfContents.astro';
const { title, headings } = Astro.props;---
<Base title={title}> <div class="kb-layout"> <SeriesSidebar /> <article> <h1>{title}</h1> <slot /> <!-- article content --> </article> <TableOfContents headings={headings} /> </div></Base>The chain is: Page → Layout → Layout → HTML. Each layer adds structure and passes content down through <slot />. No duplication — the <html> tag, header, and footer exist in exactly one file (Base.astro).
Real Example: The Full Layout Chain
This is the exact code that renders the page you’re reading right now. Toggle to see how 3 files compose into one page:
KB entry layout chain
1. Page — src/pages/[kind]/[...slug].astro
---import { getCollection, render } from 'astro:content';import KBEntry from '../../layouts/KBEntry.astro';
export async function getStaticPaths() { const entries = await getCollection('kb'); return entries.map(entry => ({ params: { kind: entry.data.kind, slug: entry.id }, props: { entry }, }));}
const { entry } = Astro.props;const { Content, headings } = await render(entry);---
<KBEntry title={entry.data.title} headings={headings}> <Content /></KBEntry>2. Layout — src/layouts/KBEntry.astro (wraps the page)
---import Base from './Base.astro';import SeriesSidebar from '../components/SeriesSidebar.astro';import TableOfContents from '../components/TableOfContents.astro';
const { title, headings } = Astro.props;---
<Base title={title}> <div class="kb-layout"> <SeriesSidebar /> <article data-pagefind-body> <h1>{title}</h1> <slot /> </article> <div class="toc-column"> <TableOfContents headings={headings} /> </div> </div></Base>3. Root Layout — src/layouts/Base.astro (wraps everything)
---import Header from '../components/Header.astro';import Footer from '../components/Footer.astro';import '../styles/global.css';
const { title } = Astro.props;---
<!doctype html><html lang="en" data-theme="dark"> <head> <title>{title} — krowdev</title> <!-- meta tags, fonts, etc --> </head> <body> <Header /> <main id="main"> <slot /> </main> <Footer /> </body></html><!-- Final compiled output (simplified) --><!doctype html><html lang="en" data-theme="dark"> <head> <title>Layouts — krowdev</title> <meta name="description" content="Template inheritance..." /> <link rel="stylesheet" href="/_astro/global.css" /> <!-- font preloads --> </head> <body> <!-- Header component (expanded) --> <header class="header"> <nav class="header-inner"> <a href="/" class="logo">...</a> <a href="/explore/" class="nav-link active">Explore</a> <a href="/guide/..." class="nav-link">Guides</a> <button class="theme-toggle">...</button> </nav> </header>
<main id="main"> <div class="kb-layout"> <!-- SeriesSidebar component (expanded) --> <nav class="series-sidebar"> <div class="sidebar-section"> <p class="section-label">Learn Astro 6</p> <ul> <li><a href="/guide/astro-mental-model/">The Mental Model</a></li> <!-- ... 8 more lessons ... --> </ul> </div> </nav>
<!-- Article content (markdown → HTML) --> <article data-pagefind-body> <h1>Layouts</h1> <p>Template inheritance — wrap pages in shared structure...</p> <h2 id="layouts-are-components">Layouts Are Components...</h2> <!-- rest of article --> </article>
<!-- TableOfContents component (expanded) --> <aside class="toc"> <p class="toc-title">On this page</p> <ul> <li><a href="#layouts-are-components">Layouts Are Components</a></li> <!-- ... more headings ... --> </ul> </aside> </div> </main>
<footer class="footer">...</footer> </body></html>The three source files compose into a single HTML page:
- Base.astro provided:
<html>,<head>, Header, Footer - KBEntry.astro provided: 3-column grid, Sidebar, ToC
- […slug].astro provided: the rendered markdown content
The <slot /> in each layout was replaced by the content from the layer above:
- Markdown → rendered HTML → placed in KBEntry’s
<slot /> - KBEntry’s output → placed in Base’s
<slot /> - Base wraps everything in
<html>→ final page
No duplication. The header exists in one file. The sidebar exists in one file. The grid layout exists in one file. Change any of them and every kb entry updates.
How Pages Use Layouts
A page component wraps its content in a layout:
---import KBEntry from '../../layouts/KBEntry.astro';
const { entry } = Astro.props;const { Content, headings } = await render(entry);---
<KBEntry title={entry.data.title} headings={headings}> <Content /> <!-- markdown rendered to HTML, placed in KBEntry's <slot /> --></KBEntry>The flow:
[...slug].astrorenders the markdown and passes it toKBEntryKBEntry.astroplaces it in a 3-column grid and passes everything toBaseBase.astrowraps it all in<html>with header and footer
Layouts vs. Components
| Layout | Component | |
|---|---|---|
| Purpose | Wraps entire pages | Reusable piece within a page |
Uses <slot />? | Always | Sometimes |
| Lives in | src/layouts/ | src/components/ |
Provides <html>? | Usually (root layout) | Never |
| Convention | Named after content type (KBEntry, Base) | Named after what it renders (Header, Badge) |
Technically they’re both .astro files with the same syntax. The distinction is organizational — layouts provide page structure, components provide reusable pieces.
Challenge: Trace the layout chain
For the page you’re reading right now (/guide/astro-layouts/), trace the full chain:
- Which page file generates this URL?
- Which layout does that page use?
- Which layout does that layout use?
- Which components appear at each level?
Read the actual files to confirm:
cat src/pages/\[kind\]/\[...slug\].astrocat src/layouts/KBEntry.astrocat src/layouts/Base.astroAnswer
src/pages/[kind]/[...slug].astro— dynamic route, one page per kb entry- Uses
KBEntry.astro— adds sidebar + content + ToC grid KBEntry.astrousesBase.astro— adds<html>,<head>, Header, Footer- Components:
- Base level: Header (→ ThemeToggle), Footer
- KBEntry level: SeriesSidebar, TableOfContents
- Page level: None (just rendered markdown)
Sources
- Astro Docs, Layouts
- Astro Docs, Slots
- Astro Docs, Markdown layouts