I have an issue that occurs only in Firefox.
On my page there is a header and a product list. The header list has some sticky components to keep it in view on the page, as the user scrolls through the product list.
However I get a bunch of extra whitespace when scrolling up:

and whenever I inspect the element or somehow invalidate it (e.g. by accessing its clientTop attribute) the whitespace collapses back to normal and to what I expect:

I tried creating a reproducible example, but came up slightly short.
In real life this issue occurs without reloading the page, and by simply scrolling up/down. However I reproduced something that visually looks the same, but is reproduced by scrolling down the page, reloading, and then scrolling back up. (I am hoping that the issue is related.)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sticky Layout</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif;
background: #fff;
}
.shell {
max-width: 1540px;
margin: 0 auto;
padding: 0 80px;
}
@media (max-width: 1199px) {
.shell {
padding: 0 30px;
}
}
@media (max-width: 767px) {
.shell {
padding: 0 20px;
}
}
.top-gap {
height: 210px;
}
.layout {
position: relative;
margin-bottom: 40px;
}
filter-bar {
display: contents;
}
.sticky-wrapper {
display: contents;
}
.sticky-layer {
display: contents;
}
.sticky-row {
z-index: 9;
float: left;
background: #fff;
display: block;
position: sticky;
top: -1px;
width: calc(100% - 100px);
}
.pill-track {
margin-left: -20px;
margin-right: -20px;
background: #fff;
}
@media (min-width: 768px) {
.pill-track {
margin-left: -30px;
margin-right: -30px;
}
}
.pill-row {
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
display: flex;
flex-wrap: wrap;
row-gap: 10px;
overflow: auto;
padding: 20px 0 20px 20px;
}
@media (pointer: coarse) {
.pill-row {
flex-wrap: nowrap;
}
}
@media (min-width: 768px) {
.pill-row {
padding: 30px 0 30px 30px;
}
}
.pill-row::-webkit-scrollbar {
display: none;
}
.pill-slot {
flex-shrink: 0;
padding-right: 8px;
}
@media (min-width: 768px) {
.pill-slot {
padding-right: 10px;
}
}
.pill-slot:last-child {
padding-right: 20px;
}
@media (min-width: 768px) {
.pill-slot:last-child {
padding-right: 30px;
}
}
.pill {
width: 96px;
height: 42px;
border: 1px solid #f5f5f5;
border-radius: 42px;
background: #f5f5f5;
}
.sticky-info {
z-index: 9;
width: 100%;
margin-bottom: 20px;
background: #fff;
width: 100px;
height: 102px;
margin-bottom: 0;
margin-right: -1px;
top: 0;
background: #fff;
display: flex;
justify-content: flex-end;
align-items: center;
position: sticky;
}
.info-block {
width: 74px;
height: 14px;
border-radius: 10px;
background: crimson;
}
.sticky-border {
--row-height: 0;
z-index: 9;
height: 1px;
top: calc(var(--row-height) - 1px);
background: #e6e6e6;
position: sticky;
}
@media (max-width: 767px) {
.sticky-border {
width: calc(100% + 40px);
margin-left: -20px;
}
}
@media (min-width: 768px) {
.sticky-border {
width: 100%;
}
}
.grid {
clear: both;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
padding-top: 20px;
}
@media (min-width: 900px) {
.grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
.box {
min-height: 240px;
border: 1px solid #ececec;
border-radius: 8px;
background: linear-gradient(135deg, #f2f4f7 0%, #fafbfc 100%);
}
</style>
</head>
<body>
<div class="shell">
<div class="top-gap"></div>
<div class="layout">
<filter-bar>
<div class="sticky-wrapper">
<div class="sticky-layer">
<div class="sticky-row">
<div class="pill-track">
<div class="pill-row">
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 1"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 2"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 3"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 4"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 5"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 6"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 7"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 8"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 9"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 10"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 11"></button></div>
<div class="pill-slot"><button class="pill" type="button" aria-label="filter 12"></button></div>
</div>
</div>
</div>
<div class="sticky-info">
<div class="info-block"></div>
</div>
<div class="sticky-border"></div>
</div>
</div>
</filter-bar>
<div id="grid" class="grid"></div>
</div>
</div>
<script>
class FilterBar extends HTMLElement {
connectedCallback() {
const stickyRow = this.querySelector(".sticky-row");
const stickyBorder = this.querySelector(".sticky-border");
if (stickyRow === null || stickyBorder === null) {
return;
}
this.observer = new ResizeObserver(() => {
stickyBorder.style.setProperty("--row-height", `${stickyRow.clientHeight}px`);
});
this.observer.observe(stickyRow);
}
disconnectedCallback() {
if (this.observer === undefined) {
return;
}
this.observer.disconnect();
}
}
customElements.define("filter-bar", FilterBar);
const grid = document.querySelector("#grid");
for (let i = 0; i < 96; i += 1) {
const box = document.createElement("div");
box.className = "box";
grid.appendChild(box);
}
</script>
</body>
</html>