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.
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
| Selection | Scope and precedence |
|---|---|
URL parameter layout=false | Omits the authored layout for this visit. |
Frontmatter layout: false | Disables an authored layout for this page. |
[@layout]: ./file.layout.md | Page-local choice overrides defaults. |
[@layout.lesson]: ./file.layout.md | Nearest folder index default for type lesson. |
[@layout.default]: ./file.layout.md | Nearest folder index fallback. |
| No selection | Uses 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.
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.
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.
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.
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.
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.
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.
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.