Skip to main content

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

Bundler
import "@luz-ds/catalog/components/page-rail.css";
HTML
<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:

HTML
<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.

ClassEffect
.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__inner
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__onpage
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__fold
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__title
On the <summary>: "On this page", semibold. It names the <nav>
.luz-page-rail__list
On the <ul>: no bullets, a rule at its start
.luz-page-rail__link with .luz-sidebar__link
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__sub
On a nested <ul>: the h3 of an h2, indented
.luz-page-rail__actions
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-top from 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 with aria-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 h2 of the page and, nested under it, each h3. 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

Do Use the words of the headings for the links, so a link says the name of its section.
Don't Number the links, as in "Part 1" and "Part 2": out of context, a list of links like these says nothing.

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.

PartColour on backgroundLightDarkHigh contrastNeeds
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.

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.