Rehoboth Builds

The hidden attribute loses to display: flex, and a test that reads el.hidden will not notice

The hidden attribute hides an element through a single rule in the browser’s own stylesheet, display: none. Any display you set in your own CSS overrides it, so a .banner { display: flex } stays on screen while el.hidden is true — and a test that checks the property passes. Test what is rendered instead. The usual one-line fix works, and has a catch of its own.

27 September 2026 · measured in headless Chrome 153.0.8010.36 on our extension’s real options page, with the stylesheet from before and after the fix

1. What the user saw

Our extension’s options page shows a banner asking for the one permission its shortcuts need. When the user granted it, the code set the banner’s hidden to true, and the banner stayed where it was: someone who had just granted the permission was still being asked for it. The “collapse” button on the templates section set hidden on the cards, and nothing moved. During the first-run tour, the old banner showed up alongside the tour’s own.

2. Why it happens

The browser’s stylesheet contains [hidden] { display: none }. In the cascade your stylesheet beats the browser’s before selector specificity is even compared, so .banner { display: flex } or .cards { display: grid } wins, and the element is laid out as if the attribute were not there.

Copying the browser’s rule into your own stylesheet is not enough by itself. [hidden] and .banner have the same specificity, so whichever rule comes later in the file wins, and a reset at the top of the file comes first. !important is what makes it hold everywhere.

3. What we measured

We loaded the options page with its scripts removed, set hidden = true on the banner and on the template cards, and read each one back three ways:

Element and stylesheetel.hiddencomputed displaycheckVisibility()
Permission banner, before the fixtrueflextrue
Template cards, before the fixtruegridtrue
Permission banner, after the fixtruenonefalse
Template cards, after the fixtruenonefalse

Every row says the element is hidden, including the two where it is on screen.

4. Test what is rendered, not what you set

el.hidden                      // true in all four rows: it reports what your code set
getComputedStyle(el).display   // 'flex' or 'grid' before the fix, 'none' after
el.checkVisibility()           // true before the fix, false after

Every one of our tests for this page read the property, so every one passed while the banner was on screen. What caught it was comparing a screenshot with the DOM. The test now reads the computed display, and has a new check that the cards are really gone after “collapse”.

5. The one-line fix, and what it breaks

[hidden] { display: none !important; }

It covers every element on the page, including ones added later, and it is what we ship. It also overrides hidden="until-found", the form of the attribute meant to keep content hidden but findable with find-in-page. The browser hides that form with content-visibility: hidden, not with display: none, and the rule above turns it back into display: none. We put two boxes with display: flex on a test page, one with plain hidden and one with hidden="until-found", and tried three stylesheets:

Your ruleplain hiddenuntil-foundwindow.find() finds its text
noneflex (on screen)flex, content hiddenyes
[hidden]{display:none !important}nonenoneno
[hidden]:not([hidden="until-found"]){display:none !important}noneflex, content hiddenyes

If nothing on your page uses until-found — nothing in our extension does — the short rule is fine. If something does, use the longer one.

How we know, and what we did not test

Both tables come from one script that drives headless Chrome and records the browser version and date with each reading. The first loads the extension’s real options.html, once with the stylesheet from the commit before the fix and once with today’s. The second uses the two-box test page.

What we did not test: the real find bar. window.find() is the closest a script can get, and it is not the same thing — it found the until-found text without opening it, and the beforematch event that the find bar fires never fired. We also measured Chrome only.

Where this came from

From the options page of Custom Keyboard Shortcuts, a Chrome extension that binds key combinations to browser actions. The bug was there from version 1.0.0 and was fixed in 1.2.0, after a screenshot showed a banner that every test said was gone. The extension is free on the Chrome Web Store.

The API and the code are the easy half. The half that costs days is the store: the slot cap on a new account, the listing text going read-only mid-review, the uninstall URL you cannot change after you ship. The ten that caught us — three of them in full, no signup.

This note was drafted with an AI model from our own bug log and a reproduction we ran in Chrome, and automated checks ran before it was published.