Layouts
How to create reusable page shells with layouts — wrapping pages in a shared header, footer, and HTML document.
What is a layout?
A layout is just an Astro component that wraps other pages. It typically provides:
- The
<html>,<head>, and<body>shell - Common
<meta>tags (title, description, Open Graph) - Shared navigation (header/footer)
- Global CSS imports
The page content gets injected into the layout via a <slot />.
Creating a base layout
---
// src/layouts/BaseLayout.astro
import '../styles/global.css'
interface Props {
title: string
description?: string
}
const { title, description = 'My Astro site' } = Astro.props
const canonicalUrl = Astro.url.href
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalUrl} />
<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:type" content="website" />
</head>
<body>
<slot /> <!-- page content goes here -->
</body>
</html>
Using a layout in a page
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro'
---
<BaseLayout title="About Us" description="Learn about our team.">
<h1>About Us</h1>
<p>We build things with Astro.</p>
</BaseLayout>
Layered layouts
You can stack layouts — a thin wrapper around the base for pages that share a header and footer:
---
// src/layouts/PageLayout.astro
import BaseLayout from './BaseLayout.astro'
import Header from '@/components/layout/Header.astro'
import Footer from '@/components/layout/Footer.astro'
interface Props {
title: string
description?: string
fullWidth?: boolean
}
const { title, description, fullWidth = false } = Astro.props
---
<BaseLayout {title} {description}>
<Header />
<main class="pt-16">
{fullWidth
? <slot />
: <div class="container py-16"><slot /></div>
}
</main>
<Footer />
</BaseLayout>
Then a page simply uses PageLayout:
---
import PageLayout from '@/layouts/PageLayout.astro'
---
<PageLayout title="Blog">
<h1>All Posts</h1>
</PageLayout>
Using layouts in Markdown / MDX
MDX files can reference a layout via frontmatter:
---
layout: "@/layouts/BlogPostLayout.astro"
title: "My First Post"
publishedAt: 2025-01-15
---
# Hello World
This is my post content.
The layout receives the frontmatter as Astro.props:
---
// src/layouts/BlogPostLayout.astro
const { frontmatter } = Astro.props
---
<article>
<h1>{frontmatter.title}</h1>
<time>{frontmatter.publishedAt}</time>
<slot />
</article>
Note: When you use Content Collections (recommended), you render MDX with the
render()function and pass the layout via the component tree instead. Thelayoutfrontmatter field is only needed for standalone.md/.mdxpages.
Named slots in layouts
Layouts can have named slots for flexible injection points:
---
// src/layouts/DocsLayout.astro
---
<div class="layout">
<aside><slot name="sidebar" /></aside>
<main><slot /></main>
</div>
<DocsLayout>
<nav slot="sidebar">
<a href="/docs/intro">Introduction</a>
</nav>
<h1>Article Content</h1>
</DocsLayout>
Passing data upward with define:vars
Layouts sometimes need to set CSS variables or script values based on props.
Use define:vars in a style or script tag:
---
const { accentColor = '#FF5D01' } = Astro.props
---
<style define:vars={{ accentColor }}>
:root {
--accent: var(--accentColor);
}
</style>