If you've opened a Matomo heatmap and found your sidebar truncated, the bottom half of a modal missing, or click markers floating over content that doesn't seem to belong to them, the clicks themselves are fine. Matomo recorded them at the right coordinates. What's broken is the screenshot underneath them. Some wrapper on the page has overflow: hidden, overflow: auto, or overflow: scroll set, and Matomo's renderer treats whatever sits past that wrapper's visible edge as if it isn't there. So the heatmap shows you a slice of the page instead of the page your visitors actually saw.
The fastest fix is a free Chrome extension we maintain called Matomo Heatmap Helper. It neutralizes overflow containers right before each capture, lets the renderer see the full document, and restores the original styles after. The rest of this post is how to do the same thing without an extension, plus a few permanent fixes worth shipping.
How to fix it without the extension
There's a console snippet that papers over the problem right before each capture, and a few permanent fixes you can ship with the site. Pick whichever fits the constraints you're working under.
List every overflow-restricted element on the page
Before you change anything, find out which wrappers are clipping. Open the page in Chrome, hit F12 to open DevTools, switch to the Console, and paste:
// Lists every element that's currently clipping content
Array.from(document.querySelectorAll('*')).filter(el => {
const s = getComputedStyle(el);
return ['hidden','auto','scroll'].includes(s.overflow)
|| ['hidden','auto','scroll'].includes(s.overflowY);
});That gives you the exact list of nodes the Matomo renderer will treat as fixed-size boxes. The usual suspects are sidebars, modals, custom scroll wrappers around <main>, and old clearfix wrappers carrying overflow: hidden for layout reasons that haven't been relevant in years.
The quick fix: strip overflow restrictions from the console
This is the snippet we paste into the browser console right before triggering Matomo's heatmap capture. It walks every element that's currently clipping and flips its overflow and overflow-y to visible. By the time Matomo serializes the DOM, nothing is hiding past a visible edge.
// Paste into the browser console right before capture.
// Turns every overflow container on the page into 'visible'.
document.querySelectorAll('*').forEach(el => {
const s = getComputedStyle(el);
if (['hidden','auto','scroll'].includes(s.overflow) ||
['hidden','auto','scroll'].includes(s.overflowY)) {
el.style.overflow = 'visible';
el.style.overflowY = 'visible';
}
});The page is going to look strange for a moment. Sidebars expand to their full content height, modals stretch off the edge of the viewport, and anything that depended on a fixed-height container suddenly doesn't have one. That's fine. You're capturing the heatmap, not browsing the site. Refresh when you're done.
That's the quick path. It works for one-off captures and the review cycles where you need a clean screenshot now and don't want to wait on a code change. For anything you'll re-capture regularly, ship one of the permanent fixes below.
Permanent CSS fix scoped to capture mode
The cleanest answer when you control the stylesheet is a heatmap-only override that turns off every overflow container the moment Matomo's tracker is loaded. One CSS block, one JS line, and you're done.
/* Edit the selectors to match the wrappers on your site */
html.heatmap-mode .sidebar,
html.heatmap-mode main,
html.heatmap-mode .modal-shell {
overflow: visible !important;
overflow-y: visible !important;
height: auto !important;
max-height: none !important;
}// Add to your site. Flips the class on whenever Matomo's tracker is on the page.
if (window._paq) document.documentElement.classList.add('heatmap-mode');Now the override only kicks in on pages where Matomo is loading anyway, which is fine for a heatmap review and invisible to the rest of your traffic. If you'd rather scope it more narrowly, gate it on a query param (?heatmap=1) or a separate stylesheet that's only included on the staging copy you point Matomo at.
Drop the nested scroll container entirely
If your page has overflow: auto on <main> or some other wrapper for "scrollytelling" reasons, the architectural fix is to drop it and let the document scroll. Matomo's renderer can reproduce document scroll, it can't reproduce a nested scroll position. You also get better mobile UX out of it (iOS Safari has its own opinions about nested scroll containers) and accessibility improves because keyboard scroll and screen-reader navigation start working the way users expect.
It's a bigger change than the CSS override, but it's the one that stops the problem from coming back the next time someone adds a new section to the layout.
Replace clearfix overflow: hidden with display: flow-root
Old clearfix wrappers carrying overflow: hidden were a workaround for containing floated children. display: flow-root does the same thing without clipping anything, and it's been safe to use across every browser worth supporting since 2018.
/* Edit the selector to match your clearfix wrapper */
.row-with-floats {
display: flow-root;
}Same containment behaviour, no clipping, and you stop fighting with the screenshot. The quick way to find candidates is to grep your CSS for overflow: hidden and check whether each one is actually meant to clip anything. Most of the time, no.
A note on the trade-off
Whichever permanent fix you pick, scope the override (a class, a query param, a heatmap-only stylesheet) instead of changing the live styles for everyone. Your visitors are seeing the layout you designed for them, and the modal that's supposed to scroll inside its own box should keep doing that for them. The override only needs to exist for the renderer Matomo points at the page.
Why Matomo can't render your overflow containers
Matomo's screenshot capture is a two-step process. First, the tracker serializes your live DOM into HTML and ships it off. Then, your Matomo server re-renders that HTML at a fixed viewport to produce the screenshot you see in the heatmap view. No browser session, no scroll position carried over from your visitor, no JS-driven layout adjustments. Just an HTML file being rendered cold from a different IP.
The renderer can reproduce document scroll, which is why long pages capture fine. What it can't reproduce is the inner scroll position of a nested overflow: auto container, because that lives in the visitor's browser and never makes it into the serialized DOM. When the renderer hits an overflow: hidden wrapper, it does what every browser does and clips. The result is a screenshot that looks like the visible slice of the page at one specific moment, with everything else gone.
The click coordinates make it worse. Matomo records clicks relative to the document, not relative to the inner scroll position of whatever container the user was scrolling. A click on the third item in a scrollable sidebar gets stored at the y-coordinate it occupied at the moment of the click. When the renderer screenshots the sidebar at scroll-top zero, that y-coordinate now belongs to the first item. The marker plots there. The click looks like it landed on the wrong link.
What's actually failing
A few patterns we keep running into:
- Sidebars and modals with their own scrollbars. The wrapper has a fixed height and
overflow: auto, the inner list is taller, and only the part visible at capture time makes it into the screenshot. - "Scrollytelling" pages with
overflow: autoon<main>or a similar wrapper. The body doesn't scroll, the inner element does, and the heatmap captures one viewport's worth of content. - Legacy clearfix wrappers carrying
overflow: hiddenthat aren't actually meant to clip anything. They were meant to contain floats. They still clip, and they still drop content out of the screenshot. - Custom dropdown menus or accordions that animate by clipping their content with
overflow: hiddenplus a height transition. The closed state is what Matomo sees. - CSS
maskorclip-pathon a container. Different property, same effect on the screenshot. The fix is the same: turn it off in capture mode.
If your heatmap is missing content or the click markers don't line up with the elements you'd expect, you're hitting at least one of these. Often more than one on the same page.
This is also the same family of problem that breaks fonts, images, and sticky headers in the same screenshot. We've written a longer post on broken Matomo heatmap screenshots that walks through the rest, and separate posts on why fonts aren't loading and why images aren't loading since those come up almost as often.
What we'd actually do
If you control the stylesheet, ship the heatmap-only override. One CSS block scoped to a class you flip on when Matomo's tracker is loaded. It's the smallest diff that solves the problem permanently and it doesn't touch what visitors see.
If the layout has a nested scroll container that's been bugging you for other reasons too, take the architectural fix and let the document scroll. You'll save yourself this problem on every future heatmap capture, and your mobile users will thank you for it.
If neither is reachable (no stylesheet access, third-party widgets you can't restyle, a CMS that won't let you ship custom CSS), the Matomo Heatmap Helper Chrome extension is what we use on client sites where we can't change the stack. It walks the same overflow containers the snippet finds, neutralizes them for the capture, and restores them after, so the page is intact for the next visitor. Free, open source, code on GitHub.
Martez is the larger project the extension came out of. It connects Matomo with Meta Ads and Google Ads so ROAS, CLV, and attribution sit next to your web analytics instead of in a separate spreadsheet. It's in private beta. Join the waitlist if that's relevant to you.
Most of the time, this is a stylesheet change away. Worth doing once.