-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Description
Astro Info
Astro v5.11.1 (also confirmed in latest main branch source)
Describe the Bug
The hidden attribute is listed in htmlBooleanAttributes in packages/astro/src/runtime/server/render/util.ts, so any truthy value is rendered as just hidden without preserving the actual string value.
This means hidden="until-found" (a valid HTML enumerated attribute value) cannot be set via dynamic attributes or spread syntax.
Static HTML in .astro templates preserves the value correctly:
<!-- ✅ Output: <div hidden="until-found"> -->
<div hidden="until-found">content</div>Dynamic attributes lose the value:
---
const props = { hidden: "until-found" };
---
<!-- ❌ Output: <div hidden> -->
<div {...props}>content</div>
<!-- ❌ Output: <div hidden> -->
<div hidden={"until-found"}>content</div>This also affects any Astro component that internally spreads props onto HTML elements — there is no way to pass hidden="until-found" through a component.
Root Cause
In packages/astro/src/runtime/server/render/util.ts:
if (htmlBooleanAttributes.test(key)) {
return markHTMLString(value ? ` ${key}` : '');
}The hidden attribute is treated as purely boolean. However, the HTML spec defines hidden as an enumerated attribute that accepts:
""or"hidden"— behaves like the boolean hidden"until-found"— hides the element but allows browser find-in-page and fragment navigation to reveal it
What's the expected result?
When hidden has the string value "until-found", it should be rendered as hidden="until-found" instead of just hidden.
A possible fix would be to special-case hidden in the boolean attribute handler:
if (htmlBooleanAttributes.test(key)) {
if (key === 'hidden' && value === 'until-found') {
return markHTMLString(` ${key}="${value}"`);
}
return markHTMLString(value ? ` ${key}` : '');
}Or more generally, allow string values to pass through for boolean attributes when the value is not simply true:
if (htmlBooleanAttributes.test(key)) {
if (typeof value === 'string' && value !== '' && value !== 'true') {
return markHTMLString(` ${key}="${escapeHTML(value)}"`);
}
return markHTMLString(value ? ` ${key}` : '');
}Impact
hidden="until-found" is used for:
- Accordion/disclosure patterns where browser find-in-page should search collapsed content
- SEO — search engines can index content inside
hidden="until-found"elements - Accessibility —
beforematchevent enables progressive disclosure
Without this fix, component libraries that wrap HTML elements (using spread) cannot use this feature.
Note: React had the same issue and fixed it in React 19.
Participation
- I am willing to submit a pull request for this issue.