Docs Layout

Three-column documentation layout: sidebar, content, TOC. data-layout="docs" on a wrapper element.

The docs layout is the scaffold for this documentation site. Three columns: a sticky sidebar nav on the left, a constrained content column in the centre, and a sticky in-page TOC on the right. On narrow viewports, the TOC hides below 1100px and the sidebar hides below 768px.

You are looking at a live demo right now. The structure wrapping this article is [data-layout="docs"].


Structure

Three children, in order: a left <aside> for the sidebar nav, an <article> for the main content, and a right <aside data-toc> for the table of contents.

<div data-layout="docs">

  <aside>
    <nav aria-label="Documentation" data-nav="sidebar">
      <small>Section</small>
      <ul>
        <li><a href="page.html" aria-current="page">Current page</a></li>
        <li><a href="other.html">Other page</a></li>
      </ul>
    </nav>
  </aside>

  <article>
    <!-- main content -->
  </article>

  <aside data-toc>
    <nav aria-label="On this page" data-nav="toc">
      <small>On this page</small>
      <ul>
        <li><a href="#section">Section</a></li>
      </ul>
    </nav>
  </aside>

</div>
Child order matters

CSS grid assigns the three columns by source order. Left aside → article → right aside. Swapping order breaks the layout.


Columns

The grid template at full width: 220px 1fr 200px. The content column takes all remaining space. Max width of the whole layout: 1400px, centred.

ColumnElementWidthBehaviour
Left<aside>220pxSticky, hides below 768px
Centre<article>1fr (max 70ch)Scrolls normally
Right<aside data-toc>200pxSticky, hides below 1100px
Content width

The centre <article> has max-width: 70ch — comfortable reading line length. It won't stretch to fill the full 1fr column, so the grid gap appears on both sides at wider viewports.


Sticky sidebars

Both <aside> elements are position: sticky with top: calc(60px + space-4) — accounting for the top nav height. They scroll with the page until they reach their sticky top offset, then lock in place.

Both have max-height: calc(100vh - 80px) and overflow-y: auto, so a tall sidebar nav will scroll internally without moving the main content.

Stickiness requires the [data-layout="docs"] wrapper to have align-items: start. Without it, the aside stretches to the full grid row height and sticky has no room to work. This is set by the stylesheet — do not override align-items on the wrapper.


Prev / Next navigation

Add a [data-role="prev-next"] block at the end of the article to link to adjacent pages. The pattern handles both previous-only, next-only, and both.

<div data-role="prev-next">
  <a href="prose.html" rel="prev">
    <small>← Previous</small>
    <span>Prose</span>
  </a>
  <a href="footer.html" rel="next">
    <small>Next →</small>
    <span>Footer</span>
  </a>
</div>

Previous aligns left; next aligns right. Either can be omitted — the remaining link holds its position. The rel attribute is meaningful to search engines and screen readers, not just to the stylesheet.


Responsive behaviour

ViewportLayout
≥ 1100pxThree columns: sidebar + content + TOC
768px – 1099pxTwo columns: sidebar + content (TOC hidden)
< 768pxSingle column: content only (both sidebars hidden)

The sidebars are hidden, not just narrowed — they disappear entirely on small screens. If sidebar navigation is critical on mobile, add a supplemental nav inside the article or use a different layout pattern.


Scroll-spy for TOC

The right sidebar TOC can track the active heading as the user scrolls. Include toc-spy.js before </body>:

<script src="../toc-spy.js"></script>

The script observes h2[id] and h3[id] inside <article> using IntersectionObserver. As each heading enters the viewport, it sets aria-current="true" on the matching TOC link. No dependencies, no configuration.

Static fallback

Without the script, set aria-current="page" on the active TOC link in HTML for a static active state. The stylesheet responds to any truthy aria-current value.


Reference

SelectorElementPurpose
[data-layout="docs"]Any blockThree-column grid wrapper
> aside:first-of-type<aside>Left sidebar (sticky)
> article<article>Main content column
> aside[data-toc]<aside data-toc>Right TOC sidebar (sticky)
[data-role="prev-next"]Any blockPrevious/Next navigation strip