Layouts

A .layout.md Source defines the HTML around a page. Place one <x-source-content /> outlet where its Blocks should appear.

[@nav.primary]: ../navigation/site.navigation.md

<header>
  <x-navigation-nav name="primary" />
</header>
<main>
  <x-breadcrumbs-trail />
  <x-source-content />
</main>

Relative links in a layout resolve beside the layout file. Styles and component imports belong there when they support the shared page structure.

Select a layout

Put site-wide defaults in the root index.md (or typed root index):

[@layout.default]: ./layouts/site.layout.md
[@layout.lesson]: ./layouts/lesson.layout.md

A color.lesson.md Source uses the lesson layout. Other types fall back to the site default. A page can select its own layout:

[@layout]: ./layouts/special.layout.md
SelectionScope and precedence
URL parameter layout=falseOmits the authored layout for this visit.
Frontmatter layout: falseDisables an authored layout for this page.
[@layout]: ./file.layout.mdPage-local choice overrides defaults.
[@layout.lesson]: ./file.layout.mdNearest folder index default for type lesson.
[@layout.default]: ./file.layout.mdNearest folder index fallback.
No selectionUses the built-in minimal article wrapper.

A folder's canonical index (including a typed index such as index.path.md) can declare its own defaults:

<!-- paths/work/index.md -->
[@layout.default]: ../layouts/work.layout.md
[@layout.lesson]: ../layouts/work-lesson.layout.md

# Work

paths/work/notes.page.md now uses the work layout without a local layout link. Resolution visits indexes from the page's storage folder toward the root. At each index, its type-specific selection wins over its default; a nearer default wins over a more distant type-specific selection. A deeper index may override these defaults. Each relative link resolves beside the index that declares it.

The index itself uses its own defaults unless it has an explicit local selection. Layout Sources only compose through explicit [@layout] links; they do not inherit folder defaults. Included content and named instances keep the host Source's storage scope. Titles, scripts, and component imports are not inherited.

To link to content without its site layout, add ?layout=false:

[Read the lesson without navigation](./color.lesson.md?layout=false)

The rendered link targets /color.lesson?layout=false. The same Source at /color.lesson still uses its authored layout. Source properties, content, components, and access rules are unchanged. Only the exact value false opts out; other values use the normal layout selection. A raw .md URL still returns Markdown.

A PathMX server omits the layout and its assets while rendering the response. Folder styles and the effective theme still apply; see Stylesheet scope. Plain static exports apply the option in the browser: JavaScript removes the layout after the ordinary document loads, so its assets may still download. For a static page that must omit its layout without JavaScript, author layout: false in that page's frontmatter.

Nest a type layout in a site layout

In a Quickstart project, replace the root paths/index.md with:

[@layout.default]: ./site.layout.md
[@layout.artist]: ./artist.layout.md

# Workshop artists

[Meet Ada](./ada.artist.md).

Create paths/site.layout.md:

<header><a href="/">Workshop artists</a></header>
<main><x-source-content /></main>

Create paths/artist.components.md. This component reads the represented artist:

<!-- componentName: artist-summary -->

# Artist summary

```html
<p>{{ source.category }} · Booth {{ source.booth }}</p>
```

Create paths/artist.layout.md. Its parent is the site layout:

[@layout]: ./site.layout.md
[@artists]: ./artist.components.md

<article>
  <artist-summary />
  <x-source-content />
</article>

Create paths/ada.artist.md:

---
category: Printmaking
booth: 12
---

# Ada's prints

Ada makes small prints of everyday places. Visit booth 12 to see her latest work.

Run bun run dev and open /ada.artist. The outer header links home; the inner layout shows Printmaking · Booth 12 above Ada's authored biography. Changing booth to 14 updates that same layout region. Other page types still use only the site layout. Each layout has one content outlet.

Style a kind of page

A layout's stylesheet accompanies only the pages that use that layout. Continue the artist example by linking styles from paths/artist.layout.md:

[@layout]: ./site.layout.md
[@artists]: ./artist.components.md
[@styles]: ./artist.css

Every .artist.md page now loads artist.css; other pages do not. A folder's landing page can take a type the same way. paths/studios/ada/index.artist.md is the /studios/ada page and uses the artist layout, while paths/studios/ada/prints.page.md keeps the site layout. One root declaration covers every artist folder added later.

For type styles that should also apply in layout-free openings and included Sources, select the type in a global or folder stylesheet instead; see Page types.

Keep represented Source data in the component

The artist layout imports artist.components.md and uses <artist-summary />. Inside that component, source means Ada's artist Source. The layout and component files do not replace her as the represented Source. Keep metadata interpolation in the literate component; raw {{ source.category }} text in layout HTML is not a template expression.

Change the component's markup to change every artist summary together. Put artist-specific assets beside the artist Source and shared layout assets beside the layout that declares them. Component data explains Block scope and explicit attributes for more specialized presentations.

Wrap the page body in a component

A layout may place <x-source-content /> inside a component, so the component can frame or enhance the page's own content:

<x-reading-frame>
  <x-source-content />
</x-reading-frame>

The component receives the rendered page as its default slot. Render it with fragment(slots.default) inside your root element. The page body stays server-owned: live edits, Actions and Block features keep updating it. To add behavior, give the component a client that enhances that server HTML without returning clientData.

A component that returns clientData owns its children, so its client would replace the page body. That page fails with an error naming the component. Place such a component beside the outlet instead.

Present with the same layout

Install Player with bun run pmx plugins add player and add <x-player-app /> once to paths/site.layout.md. Open Ada's page in Focus and Slides, advance, then return to reading. The content outlet remains the artist's content. Use your layout stylesheet to hide optional site chrome while presenting:

html[data-pmx-play-active] header { display: none; }

Scope that selector to a site-header class when other headers must stay visible. Do not hide the content outlet or Player controls. For a sticky reading header, use the viewport contract below and check heading links before and after Play.

Content and shared controls

Use semantic HTML such as header, main, and aside to organize the layout. <x-source-content /> renders the represented page's Blocks, rather than the layout Source itself. Each layout needs exactly one empty content component. It can sit inside a literate component that receives children through <slot />.

The standard plugin composition includes SourcesPlugin. Custom plugin lists using authored layouts must include it alongside LayoutPlugin; both are exported from @pathmx/core/plugins.

Core supplies navigation, breadcrumbs, <x-navigation-toc /> for a table of contents, and the color-scheme toggle. Package components need their owning Plugin installed. For example, <x-player-app /> requires Player and <x-search-trigger /> requires Search.

pmx lint checks layout declarations and outlets. Review the layout on every page type that uses it and at narrow widths.

Sticky chrome and viewport space

Use data-pmx-sticky-header on authored sticky chrome to participate in the shared viewport contract. The runtime measures its inset; the header stays visible while scrolling. A sticky opening Block sits beneath it and reserves additional anchor clearance within its Source.

Fragment navigation and viewport-bounded components share --pmx-scroll-offset-block-start, --pmx-sticky-block-start, --pmx-sticky-block-end, and --pmx-sticky-max-block-size. Customize safe gutters with --pmx-viewport-gutter-block-start and --pmx-viewport-gutter-block-end in layout CSS. The shared control dock uses the same bottom clearance. Reserve a fixed footer's height on the document root:

:root {
  --pmx-viewport-inset-block-end: 4rem;
}

The gutter and safe-area clearance are added to that height. Keep the inset in sync with your footer when its height changes.