A team hides table rows with the HTML hidden attribute, but rows carrying a class whose stylesheet rule sets display: flex stay visible. Why does hidden stop working, and what is the fix?
answer
- not a renderer flag, a stylesheet rule
- author origin beats user-agent origin
- any display declaration defeats it
- still in the DOM, still submitted
- a discoverable variant exists
basics
~20 sThe hidden attribute is not a rendering switch — browsers implement it with a user-agent stylesheet rule that sets display: none. Any author rule setting display on the same element outranks the user-agent origin and un-hides it. The fix is a stronger author rule or a state class that controls display.
solid answer
~50 s`hidden` looks like a rendering flag but it is implemented as a **user-agent stylesheet rule** roughly equivalent to `[hidden] { display: none }`. Author styles always beat the user-agent origin, so the moment your own rule sets `display` on that element — `display: flex` on the row class, for example — it wins and the element renders despite the attribute. This is the single most common `hidden` bug. Fixes, in order of preference: stop setting `display` unconditionally on elements you also hide by attribute; or make the hiding rule stronger in your own stylesheet with `[hidden] { display: none !important }`; or drop the attribute and drive visibility from one place. What `hidden` means semantically is the part worth keeping: the element is not relevant, so it is not rendered and it is removed from the accessibility tree — but it stays in the DOM, and form controls inside it are still submitted.
go deeper
Know that hidden hides an element and that it is implemented as a display: none rule from the browser's own stylesheet, which your own CSS can override.
Explain the origin rule — author declarations beat the user-agent origin, so any display declaration on the element wins — and give the [hidden] { display: none !important } defence.
Frame it as a design problem: two systems both claiming ownership of visibility. Also know what hidden means for the accessibility tree and that hidden controls are still submitted.
Decide the codebase-wide convention for visibility state — attribute or class, never both — and make the reset that protects [hidden] part of the shared baseline rather than a per-component fix.
## hidden is a style rule, not a magic flag `hidden` is a boolean global attribute meaning "this element is not currently relevant". Crucially, browsers do not implement it with a special rendering path. They implement it in the **user-agent stylesheet**, with a rule equivalent to: ```css [hidden] { display: none } ``` That single fact explains the entire class of bugs around it. Declarations from the author origin outrank the user-agent origin, so any rule you write that sets `display` on the same element defeats the attribute: ```html <tr class="row" hidden>…</tr> ``` ```css .row { display: flex } /* author origin — wins, so the row is visible */ ``` Nothing about specificity is even needed here; origin alone decides it. The element still carries `hidden`, devtools still shows the attribute, and the row is on screen anyway. Worse, the element is now in a contradictory state: it renders, but the semantics of the attribute say it should not be perceivable. ## What hidden is supposed to mean When it works, `hidden` does more than remove pixels: - The element is not rendered at all — no box, no space reserved. - It is **removed from the accessibility tree**, so a screen reader does not encounter it. This is what makes `hidden` different from merely moving something off-screen. - It stays in the DOM, so scripts can still find it and read it. - It is a *state*, not a permanent property: remove the attribute and the element returns. Two things it does **not** do, both of which get asked as follow-ups. It does not disable anything: a form control inside a hidden subtree is still submitted with its form, because only `disabled` excludes a control from submission. And it does not cascade as an override — an ancestor being hidden hides descendants only because nothing inside a `display: none` subtree renders, not because `hidden` is inherited. ## Fixing it properly In preference order: 1. **Do not fight yourself.** Stop setting `display` unconditionally on elements whose visibility you also control by attribute. Hoist the layout `display` onto a wrapper, or apply it through a class you only add when the element is shown. 2. **Strengthen the rule in your own stylesheet.** `[hidden] { display: none !important }` near the top of the author styles is the standard defensive line, and many resets ship exactly that. It is a deliberate use of `!important` to protect a semantic guarantee, not a hack to win a specificity fight. 3. **Pick one mechanism.** Either the attribute owns visibility or a class does. Two systems both claiming to control whether something is on screen is the actual defect; the CSS collision is just the symptom. ## The until-found variant HTML also defines `hidden="until-found"`, a different state for content that should be hidden but still **discoverable**. Content in that state is not rendered, but it remains findable by the browser's find-in-page and reachable by scroll-to-text and fragment navigation. When the browser locates a match inside it, it fires a `beforematch` event and removes the attribute, revealing the content so the user lands on it. The intended use is collapsed accordions and "read more" sections, where plain `hidden` makes the text unfindable. Note that this is newer than plain `hidden` and engine support arrived at different times, so verify current support before relying on it; the mechanics also differ from plain `hidden` (`until-found` content participates differently in rendering so that it can be searched), which is another reason not to assume the two behave identically. ## The diagnosis to say out loud When an interviewer describes "the hidden attribute is not hiding anything", the expected answer is one sentence: `hidden` is a user-agent `display: none` rule, and an author rule that sets `display` on the same element overrides it. Everything else follows.
- Is a text input inside a subtree marked hidden still submitted with the form?Yes. `hidden` controls rendering and accessibility exposure, not form participation. The control keeps its name and value and is serialised on submit exactly as a visible one would be. Only `disabled` removes a control from submission — and note that a disabled control is also excluded from the data even when it is fully visible.
- How does hidden differ from moving an element off-screen with CSS?`hidden` removes the element from the accessibility tree, so a screen reader never encounters it. Off-screen positioning keeps the element rendered and fully exposed to assistive technology — which is exactly why the off-screen technique is used deliberately for visually-hidden text meant to be announced. Choosing the wrong one either leaks hidden content to screen readers or silences content you meant them to hear.
- Why does hidden="until-found" exist when hidden already hides content?Because plain `hidden` makes the text invisible to the browser's find-in-page and to scroll-to-text navigation, so a user searching the page cannot reach a collapsed section. The `until-found` state keeps the content discoverable: on a match the browser fires `beforematch`, removes the attribute and reveals the content. It is aimed at accordions and collapsed "read more" blocks.
saying these in an interview costs you the question
- Thinks hidden cannot be overridden by CSS
- Uses hidden expecting it to disable form controls
- Says hidden removes the element from the DOM
- Confuses hidden with off-screen visually-hidden text
- Assumes hidden="false" makes an element visible