Sticky table of contents in WordPress: keep it in the sidebar while readers scroll

Last updated: 28 October 2026

On a long post, a table of contents that stays next to the text lets readers jump around without scrolling back to the top. WordPress has no table of contents block of its own, and most table of contents plugins put the list inside the post, where it cannot stay in view. This guide covers where the list has to live, which plugins and blocks can put it in a sidebar, the CSS that keeps it there, and the three things that usually go wrong: other widgets under it, a list taller than the window, and links that land under a fixed header.

We make a sticky widget plugin, Quicksloth Sticky Widget, so we are not neutral; most of what follows needs no plugin. The CSS behaviour was checked on 28 October 2026 in a small test page in Chromium 153, one case at a time, and so was our plugin's script (version 1.0.0, as downloaded from WordPress.org). What we say about the table of contents plugins comes from their pages and their code on WordPress.org, read the same day; we did not install them on a site. We did not repeat the test page in Firefox or Safari.

Short version

1. The list has to be in the sidebar, not in the post

A sticky element stays inside its parent: it is "stuck" only until it meets the opposite edge of its containing block (MDN, position). Table of contents plugins usually insert the list into the post content, before the first heading or after the first paragraph. Made sticky there, it stays inside the text column: in our test page a list inside the post, with position: sticky; top: 20px, did stay at the top of the window, but on top of the paragraphs it was supposed to help with.

So the list has to sit in a box that runs next to the text: the widget area of a classic theme, or the narrow column of a block theme template. Most plugins below offer a widget for that; with a sidebar widget you will often switch off automatic insertion in the post, or the list appears twice.

2. Which table of contents plugins can go in a sidebar

Some of the most used options on WordPress.org, with the figures from its plugin directory on 28 October 2026. "Own sticky option" means a setting in the free plugin that keeps the list in view.

Plugin (version)Active installsIn a sidebarOwn sticky option
Easy Table of Contents (2.0.89)600,000+Widgets Table of Contents and Sticky Sidebar Table of ContentsYes, two (below)
Table of Contents Plus (202609.1)200,000+The TOC+ widgetNone found in its front-end code
LuckyWP Table of Contents (2.1.14)90,000+Widget Table of ContentsNone found in its front-end code
Joli Table Of Contents (3.0.4)7,000+Block and shortcode"Sticky Sidebar TOC" is listed among its Pro features
SimpleTOC (7.4.1)10,000+BlockNo: its page says the highlighting "does not make the table of contents sticky"
Rank Math SEO (1.0.280)4,000,000+Block Table of Contents by Rank Math, made for the postNo
Yoast SEO (28.8)10,000,000+Its Table of contents block is marked as a Premium block in the free pluginNo

WordPress itself has no table of contents block. There is none among the core blocks of WordPress 7.1.3 or of the current development version. The Gutenberg plugin (24.2.0) includes a Table of Contents block, still marked as experimental, so it appears only on sites that run the Gutenberg plugin. Block collections such as Kadence Blocks, Spectra and Ultimate Blocks also have a table of contents block.

The Rank Math block and the Gutenberg block save the list of headings inside the block (a headings attribute in their block.json), built in the editor from the post you are writing; that is why they belong in the post. We have not tested them in a template or widget area, so check that the list shows the current post's headings before you rely on it there.

3. Easy Table of Contents: two ways to stick, both done with a script

From the code of version 2.0.89, the free plugin offers:

The Sticky TOC tab of the same settings page is something else: a table of contents that floats at the side of the window and opens with a button, not a sidebar widget. If either widget option does not stay in view on your theme, the CSS below works on Easy Table of Contents' plain widget as on any other.

4. Make the sidebar widget sticky with CSS

Find the widget's ID: right-click the list on the page, choose Inspect, and look for the box around it with an ID such as ezw_tco-2 (Easy Table of Contents), toc-widget-2 (Table of Contents Plus), lpwtoc_widget-2 (LuckyWP) or block-5 (a block in a widget area); the number changes from site to site. Put the rule on that box, not on the list inside it, and add it under Appearance → Customize → Additional CSS (classic themes):

#toc-widget-2 {
  position: sticky;
  top: 20px;
}

Then move the widget to the end of the sidebar. In our test page, a sticky list as the last widget stayed 20 px from the top and stopped where the sidebar ended, 20 px above the footer. As the first widget, with two other widgets below it, it also stayed at 20 px, and the widget under it slid up behind it: 156 px of that widget were covered after 500 px of scrolling, because a sticky element is drawn above ordinary boxes. That is the same advice Easy Table of Contents gives for its pinned widget.

If nothing happens at all, the theme is breaking one of the conditions CSS sticky needs: an overflow rule on a parent, a sidebar no taller than its widgets, or a missing top. Our page on sticky sidebar not working in WordPress shows how to find each one in DevTools and the fix.

5. A list taller than the window

A post with many headings gives a long list. In our test page, a sticky list of 40 links (1,108 px, in an 800 px window) stayed at 20 px from the top, and its last links stayed below the bottom edge of the window. Give the list a maximum height and let it scroll inside itself:

#toc-widget-2 {
  position: sticky;
  top: 20px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}

With these two lines the same list still stuck at 20 px, ended 20 px above the bottom of the window, and scrolled inside its own box down to the last link. The overflow here is on the sticky element itself, which is fine; it is overflow on a parent that stops sticky from working. If you also want fewer entries, most plugins let you choose which heading levels to include: listing only the main headings often makes the list short enough.

With a fixed header, a click on a link in the list scrolls the heading to the very top of the window, behind the header. In our test page, with an 80 px fixed header, the heading of "Section 5" ended at 0 px, under the header. Adding scroll-margin-top to the headings fixed it: the heading stopped at 100 px, 20 px below the header.

.entry-content h2,
.entry-content h3 {
  scroll-margin-top: 100px; /* 80 px header + 20 px gap */
}

Use the class of your theme's post content (.entry-content in many classic themes, .wp-block-post-content in block themes). The sticky list needs the same gap: top: 100px instead of 20px. Plugins with their own smooth scrolling may move the page themselves; Easy Table of Contents has Smooth Scroll Offset (30 px by default) and Mobile Smooth Scroll Offset under its Advanced settings for that; they apply to the jump to a heading only when its Smooth Scroll option (General settings) is on.

7. Block themes: a Group with the Sticky setting

The table of contents blocks have no sticky setting of their own (the Gutenberg block and the Rank Math block declare no position support in their block.json). WordPress offers Position → Sticky only on the Group block, and only on themes that turn it on: in our tests with WordPress 7.1.3 it worked on Twenty Twenty-Five, Astra and Kadence, also in the narrow column of a Columns block, and was missing on GeneratePress, OceanWP and Twenty Twenty-One. To use it, put the list inside a Group, in the side column, and set the Group to Sticky. The limits are the ones in our guide to making a widget sticky: the Group sticks at the very top of the window with no field for a gap (so a fixed header covers it), and it moves on only when the end of its column reaches it. A long list in a Group also needs the max-height of section 5, added as custom CSS. A Group set to Sticky inside the post is still inside the text column (section 1).

When CSS is not enough

Sometimes the theme's overflow is needed, the sidebar cannot be stretched, or you want the list to stop before the footer rather than at the end of its column. Then a script has to measure the page. We ran the script of Quicksloth Sticky Widget 1.0.0 on the same test page, with its default settings:

In a classic theme you tick Sticky (stays in view while scrolling) in the table of contents widget under Appearance → Widgets; for a block, turn on Sticky in its Sticky panel; any other element can be listed by ID or class under Custom sticky elements. The plugin is free in the WordPress.org Plugin Directory. We have not tested it on a site with each of the table of contents plugins above.

Quick check list

What you seeWhyFix
The list sticks on top of the textIt is inside the post contentUse the plugin's sidebar widget or block, and turn off insertion in the post
Other widgets slide under the listThe list is not the last widgetMove it to the end of the sidebar
The last links are off screenThe list is taller than the windowmax-height: calc(100vh - 40px); overflow-y: auto
Clicked headings hide under the headerThe header is fixedscroll-margin-top on the headings, top on the list
Nothing sticks at allA theme rule breaks CSS stickyThe six causes and fixes

More on what our plugin does and does not do and how to make any widget sticky; if you use Fixed Widget (q2w3) today, see the Fixed Widget alternative page. Still stuck with your theme? Write to support@quicksloth.dev with a link to the page.