Have you ever scrolled through a long, insightful blog post and wished you could quickly jump to a specific section? While the Wix Blog is powerful, it doesn’t currently offer a native sticky Table of Contents (TOC).
In this topic, we’ll build a dynamic, auto-generating, and sticky TOC that reads your blog headings and indents them based on their hierarchy.
Step 1: Layout Setup in Wix Studio
To make the Table of Contents stay with the reader as they scroll, we need to utilize a multi-column layout.
- Split the Section: In the Wix Studio Editor, select the section containing your Blog Post widget and split it into two columns. A great starting point for proportions is:
- Column 1 (TOC):
Min/max 200px, 1fr - Column 2 (Blog):
3fr
- Column 1 (TOC):
- The Blog Column: Place your Post Widget into the larger column. Set it to Stretch so it fills the available space.
- The TOC Column: In the smaller column, add a Container and configure it as follows:
- Set width to 100%.
- Dock it to the Top, Left, and Right with consistent margins (e.g., 16px)
- Set a Max Height (e.g., 90vh) and set Overflow Content to Scroll. This ensures that if your TOC is longer than the screen, users can still scroll through the links.
- Make it Sticky: With the container selected, go to the Inspector, and change the Position to Sticky. Set a Top Offset (e.g., 16px) so it sits comfortably below the top of the browser.
- Add a Repeater: Drop a Repeater inside that container.
- Add a button inside the repeater item to serve as the clickable link
- Important: Change the IDs in the code panel to #repeater and the button to #button.
Your layers will look something like this:
Step 2: Add the code
Now that the structure is ready, we need to “read” the blog post data and populate our repeater.
Open your Page Code panel and paste the following:
import * as wixSiteLocation from '@wix/site-location';
$w.onReady(async () => {
// Set the repeater with blank data before further steps to prevent "flashing" of temp data
$w("#repeater").data = []
// 1. Fetch the actual content data from the blog post widget
const postData = await $w("#postWidget").getPost();
const currentUrl = await wixSiteLocation.location.url();
// 2. Set up how each item in our TOC repeater should behave
$w("#repeater").onItemReady(async ($item, itemData) => {
// Set the button label to the heading text found in the post
$item("#button").label = itemData.heading;
$item("#button").target = "_self";
// Construct the anchor link using the unique ID of the heading block
$item("#button").link = `${currentUrl}#viewer-${itemData._id}`;
// Add a CSS class based on heading level (H1, H2, etc.) for indentation
$item("#repeaterItem").customClassList.add(`table-of-contents__level-${itemData.headingLevel}`);
});
// 3. Generate the data array and assign it to the repeater
$w("#repeater").data = await generateTOCData(postData.richContent);
});
/**
* Converts Wix Rich Content into an array for a Table of Contents repeater.
*/
function generateTOCData(richContent) {
// Safety check: if there's no content, return an empty list
if (!richContent || !richContent.nodes) return [];
return richContent.nodes
.filter(node => node.type === 'HEADING') // Only grab heading nodes
.map(node => {
// Extract text from child nodes (this handles bold/italic formatting within headings)
const headingText = node.nodes
.filter(child => child.type === 'TEXT')
.map(child => child.textData?.text || "")
.join("");
return {
_id: node.id,
heading: headingText,
headingLevel: node.headingData?.level || 1 // Defaults to 1 if level is missing
};
})
.filter(item => item.heading.trim().length > 0); // Ignore empty headings
}
How the Code Works
postData.richContent: This is a JSON object containing every element of your post (paragraphs, images, headings, etc.).generateTOCData: This function filters that JSON to find only headings, then maps them into a format the Repeater understands.#viewer-${itemData._id}: Wix Blog automatically assigns unique IDs to headings. By creating anchor links to these IDs, we enable the “jump-to” functionality.
Step 3: Global CSS for Hierarchy
To make the TOC look organized, we want deeper headings (like H3s) to be indented further than H2s. Add this to your Global CSS file:
/* Indentation logic based on Heading Levels */
.table-of-contents__level-1 { padding-left: 0; }
.table-of-contents__level-2 { padding-left: 12px; }
.table-of-contents__level-3 { padding-left: 24px; }
.table-of-contents__level-4 { padding-left: 36px; }
.table-of-contents__level-5 { padding-left: 48px; }
.table-of-contents__level-6 { padding-left: 60px; }
Note: Ensure the ID of the repeater item in the code (
#repeaterItem) matches the ID you set in the editor for the CSS classes to apply correctly viacustomClassList.
Summary
That’s it! You now have a fully functional, automated Table of Contents.
The Benefits:
- User Experience: Readers can navigate long-form content effortlessly.
- Dynamic: You don’t need to manually update links; if you change a heading in the Blog Manager, the TOC updates itself.
- Visual Hierarchy: The CSS ensures your subheadings look like subheadings, keeping the sidebar organized.



