AstroShowcase
Docs / Core Concepts / Layouts
Core Concepts Updated May 7, 2026

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. The layout frontmatter field is only needed for standalone .md / .mdx pages.

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>