Catalog · Documentation
Rail
The list of the sections of the page that stays in view beside the content, with the actions of the page under it.
Fits best: docs — the side of a long documentation page
Example
A page with its rail beside it: the list of sections and the three actions.
Install
Add the tool to your project with your package manager.
Configure
The tool reads one file from the root of the project.
The config file
Name it acme.config.json.
The rail beside the text it lists, shown at any width: in a page it is a third column from 80rem and sits above the content below it. With the catalog's script, the section in view is marked as you scroll.
Known issues
Without the catalog's script no link is marked as current, and the links still work. Not tested with a screen reader.
Code
Copy the markup
import "@luz-ds/catalog/components/page-rail.css";
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/page-rail.min.css" integrity="sha384-rtBwzNTcuj/dbwc7iMTxhqi7LU16M2g06o5Y73z+/VADATrf1qbSJBM+Z2b3pNjq" crossorigin="anonymous">
The rail, as a direct child of the shell, between the sidebar and the content:
<aside class="luz-page-rail" aria-label="About this page">
<div class="luz-page-rail__inner">
<nav class="luz-page-rail__onpage" aria-labelledby="onpage-title">
<details class="luz-page-rail__fold" open>
<summary class="luz-page-rail__title" id="onpage-title">On this page</summary>
<ul class="luz-page-rail__list">
<li><a class="luz-sidebar__link luz-page-rail__link" href="#install">Install</a></li>
<li><a class="luz-sidebar__link luz-page-rail__link" href="#configure">Configure</a>
<ul class="luz-page-rail__sub">
<li><a class="luz-sidebar__link luz-page-rail__link" href="#config-file">The config file</a></li>
</ul>
</li>
</ul>
</details>
</nav>
<div class="luz-page-rail__actions">
<a href="https://example.com/docs/install.md">Copy page as Markdown</a>
<a href="https://example.com/edit/install.md">Edit this page</a>
<a href="https://example.com/issues/new">Report a problem</a>
</div>
</div>
</aside>
Variants and options
No modifier. The rail is an <aside> around one box that holds two blocks: the list and the actions.
| Class | Effect |
|---|---|
.luz-page-rail | On an <aside>: the third column of the shell from 80rem, as tall as the content; below it, above the content |
.luz-page-rail | On a <div>, the one child of the rail, around the list and the actions: from 80rem it is the sticky box, inside the rail, so it never covers the footer, and it scrolls by itself, with room at its start for a focus ring |
.luz-page-rail | On the <nav> of the list, a direct child of __inner: it scopes the reset of the fold and its title, and is the hook for a script |
.luz-page-rail | On a native <details>. Write open, so the list shows without a script. Where the rail is above the content, a script may close it when the page loads |
.luz-page-rail | On the <summary>: "On this page", semibold. It names the <nav> |
.luz-page-rail | On the <ul>: no bullets, a rule at its start |
.luz-page-rail with .luz-sidebar | On each <a>: text on the page, 28px tall. The link marked aria-current="true" gets a start border in the brand colour |
.luz-page-rail | On a nested <ul>: the h3 of an h2, indented |
.luz-page-rail | A column of links under a rule: copy, edit, report |
How it works
CSS from components/page-rail.css of @luz-ds/catalog, on the catalog's sidebar link and its scroll spy.
- Below 80rem the rail is drawn above the content, under the header. From 80rem it is a column as tall as the content, and its inner box is sticky, at
--luz-page-rail-topfrom the top of the window: it stops at the end of the rail, so it never covers the footer under it. The inner box is as tall as the window at most, and scrolls by itself when the list is longer. - A link carries the catalog's
.luz-sidebar__link, so the catalog's script marks the section in view witharia-current="true". The rail redraws the link as text on the page: the mark is the weight and a start border, never the colour alone. - The list names each
h2of the page and, nested under it, eachh3. Nothing is nested deeper. - "Copy page as Markdown" is a link to the page's Markdown file; copying it with a script is yours. The rail is left out of print.
Without JavaScript
The list and the actions are links and a native <details>, so they work. No link is marked as current: the mark comes from the catalog's script.
Keyboard and ARIA
- Order: the rail is between the sidebar and the content in the source, so Tab reaches it before the text: the list, then the actions. The skip link goes to the content, past the rail.
- Keys: Enter or Space on "On this page" folds the list. Tab reaches each link; Enter goes to its section.
- Names: the
<aside>is a landmark named "About this page"; the<nav>is named by its summary, "On this page".
Contrast and focus are in the Design tab.
Design
When to use
Use the rail on pages with several sections, so a reader sees the shape of the page and jumps to a part of it.
- A page with three or more sections, long enough to scroll.
- Actions that belong to the page and not to the site: edit, report, copy.
When not to use
Do not use the rail on a short page: a list of two links is more to read than the page.
- It lists the sections of one page. The pages of the site go in the sidebar.
- Nest no deeper than an
h3. For the contents beside an article, use the CMS table of contents.
Do and don't
Writing the content
- Links: the text of the heading, word for word. Short, in sentence case.
- Title: "On this page". Translate it with the rest of the page.
- Actions: verb first, as in "Edit this page" and "Report a problem". Keep the order: copy, edit, report.
Contrast and focus
The list sits on the page surface. The link under the pointer is drawn on a muted surface.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
Title | --luz-text-primary on --luz-surface-page | 15.8:1 | 15.1:1 | 21.0:1 | 4.5:1 |
Link | --luz-text-secondary on --luz-surface-page | 6.2:1 | 7.5:1 | 19.3:1 | 4.5:1 |
Link under the pointer | --luz-text-primary on --luz-surface-muted | 15.8:1 | 15.1:1 | 20.1:1 | 4.5:1 |
Section in view | --luz-text-primary on --luz-surface-page | 15.8:1 | 15.1:1 | 21.0:1 | 4.5:1 |
Mark of the section in view | --luz-brand-primary on --luz-surface-page | 5.6:1 | 7.7:1 | 14.1:1 | 3:1 |
Action | --luz-brand-primary on --luz-surface-page | 5.6:1 | 7.7:1 | 14.1:1 | 4.5:1 |
Focus ring | --luz-focus-ring on --luz-surface-page | 5.6:1 | 7.7:1 | 21.0:1 | 3:1 |
Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.
- Focus: every link and the summary keep core's ring, 2 px of
--luz-focus-ring. - Mark: the section in view is told apart by its weight and by a 2px start border, as well as by the colour of its text.
- Target: each link, the summary and each action are at least 28px tall.
Tokens it uses
The 14 tokens that the .luz-page-rail rules of catalog’s components/page-rail.css read, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.
- Color
--luz-brand-primary,--luz-divider,--luz-surface-muted,--luz-text-primary,--luz-text-secondary- Spacing and sizing
--luz-control-sm,--luz-space-2xs,--luz-space-lg,--luz-space-md,--luz-space-sm,--luz-space-xl,--luz-space-xs- Typography
--luz-font-sans,--luz-text-sm
Keyboard and ARIA are in the Code tab.