Skip to main content

Catalog · Documentation

Shell

The page of a documentation site in three columns: the sidebar, the content and a rail, down to one column on a phone.

Fits best: docs — the layout of every page of a documentation site

Example

The shell is the whole page, so it has no live example inside another page. The markup below is a complete page: copy it, add your own content and resize the window.

It is three columns from 80rem, two from 60rem and one below, with the rail above the content when there is no third column. Documentation templates has the same page filled in.

Known issues

None known.

Code

Copy the markup

Bundler
import "@luz-ds/catalog/components/docs-layout.css";
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/docs-layout.min.css" integrity="sha384-4mL+wj5xGmF98hYy2houcLGyUl4xNNTemLvMbtsCj1A7wpQ7RE3Ng26GboK9hgm9" crossorigin="anonymous">

The shell on <body>, then the header, the sidebar, the rail, the content and the footer, in the order a keyboard reaches them. The skip link goes to the content, past the sidebar and the rail:

HTML
<body class="o-shell luz-docs-layout">
  <a class="luz-skip-link" href="#main-content">Skip to content</a>
  <header>
    <button class="luz-nav-toggle" type="button" popovertarget="docs-nav" aria-label="Menu">…</button>
    <p><a href="/">Acme docs</a></p>
  </header>
  <nav class="luz-sidebar luz-docs-nav" id="docs-nav" aria-label="Acme docs" popover>…</nav>
  <aside class="luz-page-rail" aria-label="About this page">…</aside>
  <main class="o-main luz-docs-main" id="main-content" tabindex="-1">
    <header class="luz-doc-header">…</header>
    …
  </main>
  <footer>…</footer>
</body>

Variants and options

No modifier. Three classes on native elements, and three custom properties.

Class or propertyEffect
.luz-docs-layout
On <body>, with core's .o-shell: the tracks and where each part goes
.luz-docs-nav
With the catalog's .luz-sidebar, on the <nav>: the sidebar's column
.luz-docs-main
With .o-main, on the <main>: the content's column. Its children keep to the measure
.luz-page-rail
On an <aside>: the third column from 80rem, above the content below it. See Rail
--luz-docs-measure
A line of text: 46rem. Set it on the shell in your own CSS to change it
--luz-docs-nav-w, --luz-page-rail-w
The sidebar and the rail: 16.5rem and 14rem
--luz-page-rail-top
How far from the top the rail sticks. Raise it under a sticky header

How it works

CSS from components/docs-layout.css of @luz-ds/catalog on core's shell and the catalog's sidebar. The file gives the tracks and the measure; core gives the grid, and the sidebar the drawer.

  • From 80rem there are three tracks: sidebar, content, rail. From 60rem there are two, with the rail drawn above the content. Below, one, in the order of the markup: sidebar, rail, content, footer.
  • A plain <header> goes across every track. The footer sits under the content, and the sidebar runs down beside it.
  • Between 769px and 60rem the sidebar is the first block above the content. At 768px and below it is the sidebar's drawer, a popover the menu button opens.
  • Every child of the content keeps to --luz-docs-measure, so an example is as wide as a line of text. In print the rail is left out.

Without JavaScript

Nothing changes: the layout is CSS. The drawer is a popover opened by the menu button's popovertarget, with no script.

In a browser without the popover API the sidebar stays in the page, above the content.

Keyboard and ARIA

  • Order: the order of the source; the grid does not change it. Skip link, header, sidebar, rail, content, footer. The skip link goes to the content, past the sidebar and the rail.
  • Skip link: put the skip link first, aimed at the <main>, so a keyboard user passes the sidebar.
  • Landmarks: a <header> child of <body> is the banner; name the <nav> after the site and the <aside> "About this page".
  • Keys: Enter or Space on the menu button opens the drawer; Escape closes it. The browser does it.

Contrast and focus are in the Design tab.

Design

When to use

Use the shell for the pages of a documentation site: read one after another, with a sidebar of all of them and a list of the sections of the page.

  • Your site has more pages than fit in a header.
  • Readers move through pages in order.

When not to use

Do not use the shell for a page that is not documentation: it assumes a sidebar and a content column.

Do and don't

Do Put the rail between the sidebar and the content. "On this page" comes before the text, and the skip link goes past it.
<nav class="luz-sidebar luz-docs-nav">…</nav>
<aside class="luz-page-rail">…</aside>
<main class="o-main luz-docs-main">…</main>
Don't Put the rail after the content. On a phone, "On this page" is then at the end of the page, after everything it lists.
<nav class="luz-sidebar luz-docs-nav">…</nav>
<main class="o-main luz-docs-main">…</main>
<aside class="luz-page-rail">…</aside>

Writing the content

The shell has few words of its own, and each is a name.

  • Skip link: "Skip to content".
  • Sidebar: aria-label the name of the documentation, such as "Acme docs". Not "Navigation": the role already says it.
  • Rail: "About this page". Its list is named "On this page".
  • Sidebar links: the titles of the pages, in the order a reader goes through them.
  • Menu button: "Menu".

Contrast and focus

The shell sets no colour of its own. The text sits on the page surface, and the sidebar keeps its own colours.

PartColour on backgroundLightDarkHigh contrastNeeds
Content text
--luz-text-primary on --luz-surface-page
15.8:1
15.1:1
21.0:1
4.5:1
Sidebar link
--luz-sidebar-text on --luz-sidebar-bg
10.9:1
10.0:1
14.1:1
4.5:1
Current sidebar link
--luz-sidebar-active-text on --luz-sidebar-active-bg
10.3:1
10.2:1
21.0: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 button keeps core's ring, 2 px of --luz-focus-ring.
  • Reflow: at 320px the page is one column and does not scroll sideways. It is checked in the tests.

Tokens it uses

The 4 tokens that the .luz-docs-layout rules of catalog’s components/docs-layout.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-space-3xl, --luz-space-lg, --luz-space-md, --luz-space-xl

Keyboard and ARIA are in the Code tab.