Overview
Adds a floating “On this page” menu to long pages. It reads the headings on your page and builds the list for you, so there’s nothing to type and nothing to keep in sync when you edit your content.
On desktop it sits on the right edge of the screen as a stack of small lines, one per heading. Hover over it and the full list slides open. On phones it turns into a small Contents button at the bottom of the screen that opens a sheet. The CSS and JS ship with the component, so there is nothing else to load.
1. Import Component
- Click Copy JSON above and paste it into any Etch page (Cmd/Ctrl + V on the canvas).
- Place it right before your article content, after the title and intro. It floats on screen no matter where you put it, but keyboard and screen reader users reach it in structure order. Putting it first lets them use it to jump ahead instead of finding it after the whole article.
- Make sure your content has headings (H2 and H3 by default) inside the
<main>element. If your content lives somewhere else, change the Content Selector property. - Adjust the component properties in the sidebar.
Tip: Drop it into your blog post or docs template once and every post gets its own table of contents automatically.
2. How It Works
Where to Place It
The menu looks the same wherever it sits in the structure panel, but placement still matters for accessibility. Keyboard users tab through the page in structure order, and screen readers read it in that order too. Put the component just before your content so they reach it first. It only adds one tab stop, since the links stay hidden until the menu is opened. Avoid placing it inside your site header, and keep it out of wrappers that use transform, filter or backdrop-filter, since those stop it from floating.
Builds Itself From Your Headings
Every heading it finds becomes a link. If a heading doesn’t have an ID yet, one is created from the heading text (for example, “Gear you actually need” becomes #gear-you-actually-need). Headings you already gave an ID keep it.
Shows Where You Are
As the reader scrolls, the current section is highlighted and a small marker slides down the rail to follow along. Inside the open panel or mobile sheet, a soft highlight sits behind the current section’s link and glides to the next one as you read. Hover over (or tab to) another link and the highlight slides over to it, then returns to where you are when you move away. Short sections near the bottom of the page still get their turn, even if they can’t scroll all the way to the top.
Desktop Rail, Mobile Pill
Above the breakpoint (768px by default) you get the slim rail on the right. Below it, you get a Contents pill at the bottom of the screen with a ring that fills up as you scroll. Tap it and the list opens as a sheet. You can also lock it to one style with the Mode property.
Only Shows Up When It’s Useful
If a page has fewer headings than the Minimum Headings setting (2 by default), the menu hides itself. It also fades away when your content scrolls out of view, like when the reader reaches the footer.
Stays in Sync
If your headings change after the page loads (filters, tabs, content loaded in later, or editing in the builder), the list rebuilds itself. Hidden headings and empty headings are skipped.
Smooth Jumps With Room for Your Header
Clicking a link smoothly scrolls to that heading. If you have a sticky header, set Scroll Offset so the heading doesn’t end up hidden underneath it.
Handles Messy Outlines
If you skip a level (an H2 followed by an H4), the H4 still indents just one step, so the list always looks tidy.
Accessibility
The menu is a proper navigation landmark named after its title, so screen reader users can jump straight to it. The current section is announced, Esc closes the menu, and after clicking a link, keyboard focus moves to that heading so the next Tab continues from there. On desktop, a short hover delay keeps the panel from flashing open when your cursor just passes by, and clicking the rail pins it open.
Reduced Motion
Users with prefers-reduced-motion turned on get instant jumps and no sliding or staggered animations. The link highlight still follows along, it just moves instantly instead of gliding.
The menu is hidden when the page is printed.
3. Component Properties
Each instance has a set of component properties that control its behavior.
| Property | Description | Accepted Value | Required |
|---|---|---|---|
| TOC Class | Adds classes to the .c-toc root. Useful for setting CSS variables per instance. | CSS classes | No |
| Content Selector | Where to look for headings. If it matches more than one element, the headings are combined in page order. Default: main. | CSS selector (e.g. main, .post-content) | No |
| Heading Levels | Which headings to include. Default: h2, h3. | Text, like h2, h3, 2,3 or a range like h2-h4 | No |
| Exclude Selector | Headings that match this, or sit inside something that matches it, are left out. Adding the data-toc-ignore attribute to a heading (or its wrapper) always works too. Default: empty. | CSS selector (e.g. .sidebar, .card) | No |
| Minimum Headings | Hides the menu when the page has fewer headings than this. Default: 2. | Number | No |
| Title | The label at the top of the panel. It also names the menu for screen readers. Leave it empty to hide the visible label. Default: On This Page. | Text | No |
| Numbered | Numbers the links like an outline: 1, 1.1, 1.2, 2. Default: Off. | Toggle | No |
| Mode | How the menu is shown. Responsive uses the rail on desktop and the pill on mobile. Default: Responsive. | Select: Responsive, Rail only, Pill only | No |
| Breakpoint (px) | Screens narrower than this get the mobile pill. Set it to 0 to keep the rail at every size. Only shown when Mode is Responsive. Default: 768. | Number (px) | No |
| Show Marker | Shows the sliding marker on the rail and the progress ring on the mobile pill. Hidden when Mode is Pill only. Default: On. | Toggle | No |
| Scroll Offset | How much space to leave above a heading after jumping to it. Use this if a sticky header covers your headings. A plain number is treated as pixels. Leave it empty to use your site’s existing scroll-margin-top. Default: empty. | Number or CSS length (e.g. 80, 5rem, var(--header-height)) | No |
| Update URL Hash | Adds the heading’s link (like #the-recipe) to the address bar when a link is clicked, so readers can copy and share it. It replaces the current history entry, so the back button isn’t cluttered. Default: Off. | Toggle | No |
For colors, sizes, spacing and animation timing, see the Customization page. For scripting, see the JavaScript API page.
