Navigation
A black wall label marks the chapter you are in; a tomato point marks the page.
Stable Class names and props are not expected to change before 1.0.
In this chapter 14 pages
Top bar
The top bar of this site is the component: wordmark at left, chapter tabs, tools at right, a 3px rule underneath. Where the tabs do not fit, they first set closer, then the search field drops its word; where they still do not fit, they fold into a Menu button that opens a native <details> panel. js/topbar.js measures the fit, so a site with three chapters keeps its tabs on a phone and one with nine folds them on a small laptop. Without the script the tabs fold below 72rem, and the menu still works.
<TopBar brand="John Doe" brandHref="/" links={[
{ label: "Research", href: "/research/", current: true },
{ label: "Teaching", href: "/teaching/" },
]}>
<ThemeToggle />
</TopBar>| Prop | Effect | Default |
|---|---|---|
brand | Wordmark text | Polytope |
brandHref | Wordmark link | / |
links | Chapters: { label, href, current } | |
default slot | Tools on the right: Search, the theme toggle |
Search
The search in this site's top bar is the component. It opens a panel over the page with results from the site's own Pagefind index, a static search that needs no server. Press / or Ctrl+K (Cmd+K on a Mac) to open it, the arrow keys to move through results, Enter to follow one, and Esc to close it. Without JavaScript the button is a plain link to a page you choose.
<TopBar brand="John Doe" brandHref="/" links={links}>
<Search href="/publications/" placeholder="Search papers, talks, and notes"
links={[{ label: "Publications", href: "/publications/" }, { label: "Teaching", href: "/teaching/" }]} />
<ThemeToggle />
</TopBar>Build the index after the site build, and mark what it should read: the main content of each page, and the kind of each page, which the panel shows beside the result. Navigation, footers, and forms are left out on their own.
npx astro build && npx pagefind --site dist<main data-pagefind-body>
<article data-pagefind-meta="kind:Publications">...</article>
</main>| Prop | Effect | Default |
|---|---|---|
href | Where the button goes without JavaScript | # |
label, placeholder | The button's text and the field's placeholder | Search |
bundle | Folder of the Pagefind files, with the site's base path | /pagefind/ |
baseUrl | Prefix for result links when the site lives under a base path | / |
hint, links | A line and quick links shown before the reader types | |
ranking | Pagefind ranking options, such as { pageLength: 0.2 } so short records do not crowd out pages |
A record can carry meta.summary, which the panel shows in place of an excerpt. These docs add every token, class, and component that way, from a short script that uses Pagefind's Node API after the build.
Chapter tabs
<nav class="ply-cluster" style="--ply-cluster-gap: 0.25rem" aria-label="Example chapters">
<a class="ply-tab" href="#" aria-current="page">Research</a>
<a class="ply-tab" href="#">Teaching</a>
<a class="ply-tab" href="#">Notes</a>
<a class="ply-tab" href="#">CV</a>
</nav>Mark the current tab with aria-current="page"; the black label comes from that attribute, so the visual state and the announced state cannot disagree.
Sidebar
<nav class="ply-sidenav" aria-label="Example sections">
<p class="ply-sidenav__heading">Foundations</p>
<ul>
<li><a href="#">Color</a></li>
<li><a href="#" aria-current="page">Typography</a>
<div class="ply-toc">
<ul aria-label="On this page">
<li><a href="#" aria-current="true">Typefaces</a></li>
<li><a href="#">Scale</a></li>
</ul>
</div>
</li>
<li><a href="#">Motion</a></li>
</ul>
</nav>The page's own sections hang under the current page, on a hairline. With Astro, put them in the current slot, and the auto list fills itself:
<SideNav groups={groups}>
<div slot="current" class="ply-toc" data-auto><ul aria-label="On this page"></ul></div>
</SideNav>On this page
The list under the current page in this page's sidebar, and in the rail at the left of an article. Give it data-auto and an empty <ul>, put data-toc-scope on the article, and the script fills it from the h2 and h3 headings and follows your scroll. With Astro and Markdown headings, <Toc headings={headings} /> renders it on the server instead.
Breadcrumb
<ol class="ply-breadcrumb">
<li><a href="#">Research</a></li>
<li><a href="#">2026</a></li>
<li aria-current="page">Higher-order message passing</li>
</ol>Pager
<nav class="ply-pager" aria-label="Pages" style="margin-top:0">
<a href="#" rel="prev"><span>Previous</span><strong>Typography</strong></a>
<a class="ply-pager__next" href="#" rel="next"><span>Next</span><strong>Motion</strong></a>
</nav>