The same evening I shipped this, Chrome opened "New in DevTools" and proudly announced DevTools for agents: AI that reads your CSS rules. Thanks Google, I'll take it as a sign I'm on the right track. Their version is for AI agents. Mine is for a 53-year-old guy pressing F6ππππππ.
We've all done it. A popup stays behind the header, so you write z-index: 999. Then 9999. Then 99999. Nothing changes.
The problem is almost never the element you're fixing. It's one of its ancestors.
The invisible box
When an element has a transform, an opacity below 1, a filter, or a position with a z-index, it creates a stacking context. Think of it as a closed box: its children can only stack inside the box. A child with z-index: 99999 is still only as high as the box itself.
DevTools won't tell you which ancestor is the box. You have to climb the tree by hand.
The idea came from a comment
After my last post about BeeLadybug's CSS hunt, a reader suggested in the comments: scan the ancestors with getComputedStyle, flag whatever creates a stacking context, then group it by cause, like the click-blocker lines. He also warned that will-change is often set on purpose for animations, so it should be a hint, not a verdict.
So that's exactly what 0.5.4 does.
What it looks like
On the demo page:
div.hero creates stacking context (transform) - z-index of 3 children is trapped
Click the line and it lists span.b1, span.b2, span.b3.
will-change stays a grey info line, with the z-index note on the same line:
div.layer-hint will-change: opacity β z-index of 1 child stays in this layer
Then I ran it on my own site
I tested it on my live HTML5 games portal. Logged in, it flagged the WordPress admin bar blocking clicks (only I see that). In incognito, only this was left:
div.entry-content creates stacking context (z-index) - z-index of 1 child is trapped
And the INSPECT section went one step further. It showed the rule responsible, inside my LiteSpeed-minified CSS:
.site-header, .main-navigation, .entry-content, .griglia-giochi-spc {
position: relative !important;
}
One !important rule from my own custom CSS, creating four boxes at once. It's not a bug today, since each box has a single child. But the day I add a dropdown that must cover the header, I already know where to look.
That's the point: not "something is wrong", but "this line caused it".
Try it (F12, paste in the console)
import('https://cdn.jsdelivr.net/npm/bee-ladybug@0.5.4/src/index.js')
.then(({ BeeLadybugCore, WebDOMAdapter, CSSAdapter }) => {
const bee = new BeeLadybugCore();
new WebDOMAdapter(bee).attach(document.body);
new CSSAdapter(bee).attach();
});
Press F6 for the CSS hunt, S to scan the page. Zero dependencies, no extension.
Next time your z-index "doesn't work", let the ladybug find the box. π
Top comments (2)
The "closed box" analogy is the best way to describe stacking context traps.
Manually climbing up the DOM tree in DevTools checking parent nodes for stray
transform,filter, oropacityvalues is one of the most tedious rituals in CSS debugging. Pointing directly to the culprit rule in the stylesheets and listing which children are trapped - while treatingwill-changeas an informational hint rather than a false positive, is a massive time-saver. Love that it runs straight from the console with zero setup!Thank you! "Closed box" was the only way I could explain it to myself too: z-index looks right, but the parent has already sealed it inside. Walking up the DOM by hand is exactly the ritual I wanted to skip, and will-change felt unfair to flag as a bug, so it stays a hint. Glad the console-only setup clicked. If you try it on a real project and it misses something, I'd love to hear about it.