Keep supporting material in a reference sidebar, or open a Source in an overlay without leaving the current page. Layouts supply the destinations; links and browser code share them.
Use a Quickstart project: its default
paths/site.layout.md already provides reference and overlay destinations.
Create paths/reference.page.md:
# Keep these ideas handy
Contrast helps readers distinguish a heading from its surroundings.
Create paths/lesson.page.md:
# Practice contrast
Try a dark heading on a light page.
[Read normally](./reference.page.md)
[@link(open=reference) Keep this handy](./reference.page.md)
[@link(open=overlay).button Open the reference](./reference.page.md)
Run bun run dev and open /lesson.page. “Read normally” navigates; “Keep this
handy” opens beside the lesson; “Open the reference” opens an overlay. Close it
to return to the invoking link. On a phone the reference uses a sheet.
The opening choice composes with button appearance. These remain normal anchors with canonical destinations for copying, new tabs, and navigation without JavaScript. Explicit reference links also work on touch screens.
Shift-click takes precedence over an authored overlay request. It also works inside an open reference or overlay. Opening a reference from an overlay dismisses the overlay so the reference is visible.
Ordinary Source links inside an opening navigate the main page. An overlay closes on that navigation; references remain when the next layout supports them. External links, downloads, and other modified clicks keep their normal behavior.
A Component rendering an anchor directly must provide both markers, plus a canonical rendered URL:
<a href="/practice.page#learner-prompts"
data-pmx-source-link
data-pmx-source-open="reference">Learner prompts</a>
Include <x-source-references /> in every layout where that link appears.
data-pmx-source-link enrolls the anchor in Source navigation;
data-pmx-source-open chooses the destination. Markdown directives supply both.
When a layout lacks the requested destination, links fall back to navigation. Direct browser API calls reject instead of navigating away.
Sources is included in Core's default setup. Projects created with the Quickstart
already include both destinations in paths/site.layout.md; no extra package
or Plugin activation is required. Shift-click an internal Source link to try
the reference sidebar.
A layout places the reference sidebar and declares overlay availability:
<div class="workspace">
<main><x-source-content /></main>
<x-source-references />
</div>
<x-source-overlay />
Use your layout's stylesheet to arrange the main content and sidebar:
.workspace {
display: flex;
align-items: flex-start;
gap: 2rem;
}
.workspace > main {
flex: 1;
min-width: 0;
}
The desktop panel docks to the right edge with compact, contiguous Source headers. Drag its left edge to resize it. The focused divider also accepts Left/Right arrow keys, Home, and End; double-click it to restore the layout’s default width. Your chosen width lasts through enhanced navigation in the current session. Headers and Source previews share the Source type’s icon, label, and color, with its description available on hover. Types without a Map definition use their resolved default label and icon. Map edits update headers already on screen. Use a header’s chevron to collapse or expand that Source; its inputs and reading position remain intact. Reopening a collapsed reference link expands it.
The reference component takes no space until a reference is open. Its default
width is clamp(18rem, 32vw, 28rem); set --pmx-source-reference-width in the
layout to adjust it. On narrow screens references appear in a dismissible sheet.
An icon button reopens the sheet while references exist.
Make the reading layout respond to its own available width, rather than only the browser width. A wide reference can leave a narrow reading pane on a desktop. If that pane has a table-of-contents column, collapse the grid along with the table of contents; hiding its contents alone leaves an empty column.
.workspace > main {
container: reading / inline-size;
}
@container reading (max-width: 48rem) {
.reading-grid {
display: block;
}
.reading-grid > .toc {
display: none;
}
}
Here .reading-grid and .toc are elements inside main. Keep the responsive
rule after the layout's desktop rules so the narrow layout wins.
At viewport widths of 48rem or less, the reference becomes a modal sheet: the reading page keeps its full width, the resize divider is hidden, and Hide references returns to the lesson without discarding the open documents. The reopen button restores the sheet and its inputs. Closing a particular reference removes that document. Desktop/mobile transitions keep the same mounted documents, so resizing does not erase an in-progress exercise.
Keep reusable content presentation in the Source or its components: for example, the bookcase uses a card component for its cover and notes, while its page layout supplies site navigation and a back link. Use container queries when the content needs to adapt to a narrow reference panel.
Opened Sources show their authored content and functioning components without
their standalone site layout. The iframe requests the
layout=false presentation of the same Source.
Each has an independent document context, keeping
IDs, inputs, Actions, and live updates separate. Expanded references share the available dock or mobile sheet height. Each document
scrolls independently; collapsed references use only their header height. When
there are too many expanded references to read comfortably, the dock itself scrolls.
For custom browser behavior, add a control to paths/lesson.page.md:
<button id="open-reference" type="button">Open the reference</button>
Create paths/scripts/open-reference.js and attach it in paths/lesson.page.md
with [@script]: ./scripts/open-reference.js:
import { openSource } from "@pathmx/core/sources/browser"
document.addEventListener("click", async (event) => {
const button = event.target instanceof Element
? event.target.closest("#open-reference")
: null
if (!button) return
try {
const opened = await openSource("./reference.page.md", {
in: "overlay",
from: button,
})
// An owning interaction may later call opened.close().
} catch (error) {
console.error("The reference could not be opened.", error)
}
})
The delegated listener also handles the control after a live content update. Choose it to open the same reference through the browser API.
in accepts reference or overlay. from supplies the represented Source for
relative references and the invoking control for focus return. Without from,
pass a canonical Source reference or absolute URL.
The promise resolves after the Source mounts and returns an idempotent
close() handle. A handle for replaced overlay content cannot dismiss its
replacement. An optional signal cancels a pending opening:
const controller = new AbortController()
const opening = openSource("/reference.page", {
in: "reference",
signal: controller.signal,
})
// Cancel while loading if the owning interaction is no longer relevant.
controller.abort()
await opening.catch(() => {})
Load failures leave a retry action and an ordinary-link fallback in the surface. Closing a loading surface cancels its pending opening.
References are independent: closing one leaves the others in place. Reopening the same Source and query reveals its existing reference; a different fragment moves within that reference. Different query parameters can open separate experiences.
Opening a reference or overlay does not change the main URL or add history entries. Browser Back navigates the main page. Escape and close controls dismiss an overlay; on narrow screens Escape also dismisses the reference sheet.
References survive enhanced navigation between layouts that support them. A layout without the reference component closes them. Reloads and full-document navigation do not restore references. An overlay closes when the main page navigates.
Overlays fit their content, with a 16rem minimum height and a viewport limit. Long Sources scroll inside the overlay; images, live edits, and width changes update its height automatically.
For a Source that should navigate entirely within its own viewport, use
<x-source-embed /> instead.