Skip to content

fix: [#2229] Fix Element.setAttribute() to accept names containing a colon (e.g. hx-on:click) - #2241

Open
marciomazza wants to merge 2 commits into
capricorn86:masterfrom
marciomazza:fix/attr-name-colon-prefix
Open

fix: [#2229] Fix Element.setAttribute() to accept names containing a colon (e.g. hx-on:click)#2241
marciomazza wants to merge 2 commits into
capricorn86:masterfrom
marciomazza:fix/attr-name-colon-prefix

Conversation

@marciomazza

Copy link
Copy Markdown

Description

Resolves #2229

Document.createAttribute() (and, through it, Element.setAttribute()'s HTML-namespace path) was splitting the qualified name on : into prefix/localName, even though that's only correct for createAttributeNS()/setAttributeNS(). Per spec, plain createAttribute() must keep the qualified name whole, with prefix = null and no namespace.

This produced an Attr with a prefix but no namespaceURI — a combination NamedNodeMap#getNamedItemNS() correctly refuses to resolve, since real namespaced attributes always have both. That made setNamedItem() unable to find the previous attribute to replace, so it kept appending instead of overwriting: getAttribute() on any colon-containing, non-namespaced attribute name (e.g. hx-on:click, used by htmx 4, or any custom foo:bar-style attribute) would forever return the very first value ever set via setAttribute(), no matter how many times it was called afterward.

Fix: createAttribute() now always sets localName to the full qualified name and prefix to null, matching the DOM spec and real browser behavior. createAttributeNS()/setAttributeNS() are untouched — they already implement the namespace-aware split correctly.

Also tidied up three existing tests (Element.test.ts / NamedNodeMap.test.ts) that were titled around "same local name, different prefix" but exercised via plain setAttribute() — which never namespaces anything, so they weren't actually testing that case. Rewrote them to use setAttributeNS() with real distinct namespaces, which was previously uncovered.

Before submitting the PR, please make sure you do the following:

  • Read the contributing guidelines.
  • It's really useful if your PR references an issue where it is discussed ahead of time.
  • Please check Allow edits by maintainers to make review process faster. Note that this option is not available for repositories that are owned by Github organizations.

Tests

  • Make sure to add tests for your changes. Run your test in a real browser to make sure that the test tests what a real browser would do (e.g. by running the code in the browser console).
  • Run the tests with npm test locally to make sure that all tests pass before submitting the PR.

Title

  • The title of the pull request should be in the format of "type: [#issue] description". The type can be feat, fix, chore or BREAKING CHANGE. The issue is optional and can be omitted if the pull request does not relate to an issue.
  • The title should be concise and descriptive. The title will be used when generating release notes. Make sure that the title is easily understood by users of the library.

AI disclosure

Per the project's AI Contributions policy: Claude (Anthropic) assisted in diagnosing the bug, writing the fix, and drafting this description. I guided and reviewed the change myself at every step.

@marciomazza
marciomazza requested a review from capricorn86 as a code owner July 18, 2026 12:38
…n in the name

Per spec, only createAttributeNS()/setAttributeNS() split a qualified name on
':' into prefix/localName. The plain (non-NS) createAttribute() must keep the
whole name as localName with no prefix and no namespace.

Splitting unconditionally made NamedNodeMap#getNamedItemNS() unable to find
the previous attribute to replace (its 'prefix without namespaceURI' guard
correctly rejects malformed input, but createAttribute() was manufacturing
exactly that malformed shape). setNamedItem() would then append instead of
replace, so getAttribute() on any colon-containing attribute name (e.g.
hx-on:click, used by htmx) kept returning the first value ever set, no
matter how many times setAttribute() was called afterward.
@marciomazza
marciomazza force-pushed the fix/attr-name-colon-prefix branch from 0575e36 to 18efd79 Compare September 4, 2026 20:31
…ttributeNS

Element.test.ts:1870 and NamedNodeMap.test.ts:54/114 were titled around
'different prefix' but used plain setAttribute('ns1:key', ...), which never
namespaces anything - after the createAttribute() fix those calls just
produce two unrelated attribute names that happen to contain colons.

Rewrite them to use setAttributeNS() with distinct namespaces so they
actually exercise the same-localName/different-prefix/different-namespace
case, which had no coverage anywhere else in the suite.
@marciomazza
marciomazza force-pushed the fix/attr-name-colon-prefix branch from 18efd79 to 3cd1cde Compare September 8, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

setAttribute() never updates value for attribute names containing a colon (e.g. hx-on:click)

1 participant