How htmx Resolves hx-* vs data-hx-* Attributes: Implementation Details
htmx treats both hx-* and data-hx-* attribute prefixes as interchangeable by using a centralized resolution helper that checks for the native form first, then falls back to the data- attribute form.
The bigskysoftware/htmx library accepts both hx-trigger and data-hx-trigger syntaxes to accommodate varying HTML validation requirements and developer preferences. According to the source code in src/htmx.js, htmx normalizes these dual prefixes through a single internal helper function, ensuring consistent behavior regardless of which syntax you choose.
The Attribute Resolution Algorithm in src/htmx.js
At the core of htmx's attribute handling lies a resolution routine implemented in src/htmx.js (around line 350). The resolveAttribute(elt, name) function constructs a composite CSS selector that matches both attribute forms, then traverses the DOM to locate the nearest applicable element.
The function builds the selector pattern:
'[hx-' + name + '], [data-hx-' + name + ']'
This selector is passed to element.closest(), enabling htmx to check both the element itself and its ancestors for attribute definitions. The resolution logic explicitly prioritizes the shorter syntax:
// Simplified representation from src/htmx.js (lines 350–360)
function resolveAttribute(elt, name) {
var selector = '[hx-' + name + '], [data-hx-' + name + ']';
var target = elt.closest(selector);
// Prefer hx-* over data-hx-*
return target && (target.getAttribute('hx-' + name) ||
target.getAttribute('data-hx-' + name));
}
Because every htmx feature uses this single resolution point, triggers, swaps, targets, and extensions automatically work with either syntax without additional branching code.
Priority Order and Precedence Rules
When both attributes exist on the same element, htmx follows a strict precedence hierarchy. The resolution helper evaluates target.getAttribute('hx-' + name) before falling back to target.getAttribute('data-hx-' + name), meaning the bare hx-* form always takes priority.
This behavior ensures backward compatibility and predictable overrides:
<div hx-get="/api/primary" data-hx-get="/api/backup">
<button>Click triggers /api/primary</button>
</div>
In this scenario, htmx ignores the data-hx-get value entirely because the hx-get attribute is present and checked first.
Why htmx Supports Both Syntaxes
HTML5 Validation Compliance: Environments with strict validation policies or legacy content management systems often reject custom attributes lacking the data- prefix. The data-hx-* form guarantees standard HTML5 compliance.
Developer Ergonomics: Teams preferring concise markup can use the shorter hx-* syntax without sacrificing functionality. Both forms resolve to identical internal values through the centralized resolveAttribute routine.
Practical Implementation Examples
You can safely intermix both syntaxes within the same project or DOM hierarchy:
<!-- Standard syntax -->
<button hx-get="/users" hx-trigger="click">Load Users</button>
<!-- Data attribute syntax for strict validators -->
<button data-hx-get="/users" data-hx-trigger="click">Load Users</button>
<!-- Parent uses standard, child uses data- syntax -->
<div hx-target="#result">
<button data-hx-get="/data">Fetch</button>
</div>
The closest() traversal means attributes inherit down the tree regardless of which prefix syntax you employ at each level.
Key Source Files
src/htmx.js: Contains theresolveAttributefunction (lines 350–360) and the selector construction logic'[hx-' + name + '], [data-hx-' + name + ']'.dist/htmx.js: Production build containing the same resolution logic for runtime use.test/attributes/: Test suites confirming identical behavior betweenhx-*anddata-hx-*variants across all attribute types.
Summary
- Dual syntax support: htmx accepts both
hx-*anddata-hx-*prefixes through a unified resolution mechanism insrc/htmx.js. - Priority hierarchy: The bare
hx-*attribute always takes precedence overdata-hx-*when both are present on the same element. - DOM traversal: The
resolveAttributefunction useselement.closest()to check ancestors, supporting inherited configurations with either syntax. - Zero functional difference: Both forms produce identical behavior; the choice depends solely on your HTML validation requirements.
Frequently Asked Questions
Can I mix hx-* and data-hx-* attributes on the same element?
Yes, though hx-* always wins. The resolveAttribute function explicitly checks getAttribute('hx-' + name) before falling back to the data-prefixed version, so the shorter syntax acts as an override.
Which syntax takes priority if both are present?
The hx-* form takes priority. As implemented in src/htmx.js, the resolution helper returns the value of the native attribute immediately if found, only consulting data-hx-* if the primary attribute is absent.
Is the data-hx-* syntax required for HTML validation?
Only if you operate under strict HTML5 validators that reject custom attributes. The data- prefix is always valid HTML5, while bare hx-* attributes may require a custom DTD or relaxed validation in conservative environments.
Does htmx check parent elements when resolving attributes?
Yes. The resolution algorithm uses elt.closest('[hx-' + name + '], [data-hx-' + name + ']') to traverse up the DOM tree. This means you can define attributes on parent containers and have them apply to child elements, regardless of whether you use hx-* or data-hx-* syntax at each level.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →