Skip to content

Page Layouts

Let editors pick a layout per page, for example Default, Full Width, or Landing Page. A select field stores the choice, and the page route maps that value to an Astro layout component.

  1. Add a template select field to your pages collection
  2. Create layout components for each option
  3. Map the field value to a layout in your page route

This uses EmDash’s select field and Astro’s component model.

In the admin UI, add a select field to your pages collection with slug template and your layout options (for example, “Default” and “Full Width”). To define the same field in seed data, add the following object to the collection’s fields array:

.emdash/seed.json
{
"slug": "template",
"label": "Template",
"type": "select",
"validation": {
"options": ["Default", "Full Width"]
},
"defaultValue": "Default"
}

Each layout wraps content in your base layout with different styling:

src/layouts/PageDefault.astro
---
import type { ContentEntry, InferCollectionData } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "./Base.astro";
interface Props {
page: ContentEntry<InferCollectionData<"pages">>;
}
const { page } = Astro.props;
---
<Base title={page.data.title}>
<article class="page-default">
<h1>{page.data.title}</h1>
<PortableText value={page.data.content} />
</article>
</Base>
<style>
.page-default {
max-width: var(--content-width);
margin: 0 auto;
padding: 2rem 1rem;
}
</style>
src/layouts/PageFullWidth.astro
---
import type { ContentEntry, InferCollectionData } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "./Base.astro";
interface Props {
page: ContentEntry<InferCollectionData<"pages">>;
}
const { page } = Astro.props;
---
<Base title={page.data.title}>
<article class="page-wide">
<h1>{page.data.title}</h1>
<PortableText value={page.data.content} />
</article>
</Base>
<style>
.page-wide {
max-width: var(--wide-width);
margin: 0 auto;
padding: 2rem 1rem;
}
</style>

In your page route, import each layout and map the template value:

src/pages/pages/[slug].astro
---
import { decodeSlug, getEmDashEntry } from "emdash";
import PageDefault from "../../layouts/PageDefault.astro";
import PageFullWidth from "../../layouts/PageFullWidth.astro";
const slug = decodeSlug(Astro.params.slug);
if (!slug) {
return Astro.redirect("/404");
}
const { entry: page } = await getEmDashEntry("pages", slug);
if (!page) {
return Astro.redirect("/404");
}
const layouts = new Map([
["Default", PageDefault],
["Full Width", PageFullWidth],
]);
const Layout = layouts.get(page.data.template ?? "") ?? PageDefault;
---
<Layout page={page} />

The route stays small. Each layout component owns its own markup and styling. Adding a layout is: create a component, add the option to the select field, add a line to the map.

Common layout choices:

  • Default — narrow content column, good for reading
  • Full Width — wider content area, no sidebar
  • Landing Page — no header/footer, hero sections
  • Sidebar — content with a widget area beside it

Each is just another Astro component in your src/layouts/ directory and another entry in the route’s layout map.