How htmx Handles Attribute Inheritance: hx-inherit and hx-disinherit Explained

htmx evaluates attribute inheritance through the getAttributeValueWithDisinheritance function in src/htmx.js, which enables parent attributes to cascade to children by default while respecting hx-disinherit opt-outs and hx-inherit opt-ins when global inheritance is disabled.

htmx attribute inheritance allows ancestor elements to share configuration attributes with their descendants, reducing repetitive markup in hypermedia-driven interfaces. According to the bigskysoftware/htmx source code, this mechanism relies on a DOM traversal algorithm that checks for inheritance exceptions at each level. Whether you need to prevent specific attributes from bubbling down or explicitly enable inheritance in restricted environments, understanding the interaction between hx-inherit, hx-disinherit, and the htmx.config.disableInheritance flag is essential for building maintainable applications.

How Default Attribute Inheritance Works

By default, htmx automatically inherits attributes from parent elements. When an element triggers a request, htmx walks up the DOM tree looking for configuration attributes like hx-target, hx-select, or hx-swap. This cascading behavior minimizes attribute duplication across nested components.

The inheritance chain terminates when htmx encounters an explicit value on the current element or when an ancestor has disabled inheritance for that specific attribute. The default mode operates without requiring any special attributes, making parent-level configuration immediately available to all nested elements.

The Inheritance Algorithm in src/htmx.js

The core logic resides in src/htmx.js, specifically within the getAttributeValueWithDisinheritance function (lines 466–482). This routine determines whether an ancestor's attribute should be inherited based on the current configuration state.

getAttributeValueWithDisinheritance Implementation

When htmx inspects an element's ancestors, it calls getAttributeValueWithDisinheritance with three parameters: the initial element, the current ancestor being inspected, and the attribute name being sought.

// src/htmx.js – lines 466-482
function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) {
    const attributeValue = getAttributeValue(ancestor, attributeName)
    const disinherit = getAttributeValue(ancestor, 'hx-disinherit')
    var inherit = getAttributeValue(ancestor, 'hx-inherit')
    if (initialElement !== ancestor) {
        // When global inheritance is disabled (htmx.config.disableInheritance)
        if (htmx.config.disableInheritance) {
            // only allow the attribute if the ancestor explicitly declares it
            // with hx-inherit="*" (all) or hx-inherit="attr1 attr2 …"
            if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) {
                return attributeValue                // inherited
            } else {
                return null                           // not inherited
            }
        }
        // Normal mode – honour hx-disinherit
        // If the ancestor declares hx‑disinherit and the current attribute is listed,
        // the value is overridden with the sentinel string "unset"
        if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) {
            return 'unset'                           // disinherited
        }
    }
    return attributeValue                            // default inheritance
}

This function operates in two distinct modes:

  • Normal mode (default): Inheritance is enabled globally, and hx-disinherit blocks specific attributes
  • Restricted mode: Inheritance is disabled globally via htmx.config.disableInheritance, and hx-inherit selectively enables it

getClosestAttributeValue and the 'unset' Sentinel

The DOM traversal is handled by getClosestAttributeValue (lines 90–98), which walks up the tree until it finds a definitive value:

// src/htmx.js – lines 90-98
function getClosestAttributeValue(elt, attributeName) {
    let closestAttr = null
    getClosestMatch(elt, function(e) {
        return !!(closestAttr = getAttributeValueWithDisinheritance(elt, asElement(e), attributeName))
    })
    if (closestAttr !== 'unset') {
        return closestAttr
    }
}

The string 'unset' serves as a sentinel value indicating that an ancestor explicitly forbids inheritance of that attribute. When getAttributeValueWithDisinheritance returns 'unset', the traversal continues upward, effectively ignoring that ancestor's value for the attribute in question.

Using hx-disinherit to Block Inheritance

When inheritance is enabled (the default), hx-disinherit prevents specific parent attributes from cascading to descendants. This attribute accepts either a space-separated list of attribute names or the wildcard * to block all inheritance.

Consider a scenario where a parent defines a target but a child should not inherit it:

<div hx-target="#out" hx-disinherit="hx-target">
  <button hx-get="/msg">Fetch</button>
</div>

In this example, the button does not inherit the parent's hx-target="#out" because the parent explicitly disinherits it. The getAttributeValueWithDisinheritance function returns 'unset' when it encounters this declaration, causing htmx to continue searching up the DOM tree or use default values.

You can also block multiple specific attributes:

<div hx-target="#out" hx-select="#content" hx-disinherit="hx-target hx-select">
  <button hx-get="/msg">Fetch</button>
</div>

Or disable all inheritance from a node using the wildcard:

<div hx-target="#out" hx-disinherit="*">
  <button hx-get="/msg">Fetch</button>
</div>

Using hx-inherit When Global Inheritance Is Disabled

When htmx.config.disableInheritance is set to true, the default behavior inverts: attributes no longer cascade automatically. In this mode, hx-inherit acts as an explicit opt-in mechanism, allowing specific attributes (or all attributes) to be inherited by descendants.

Enable restricted mode globally:

htmx.config.disableInheritance = true;

Then selectively enable inheritance for specific attributes:

<div hx-target="#out" hx-inherit="hx-target">
  <button hx-get="/msg">Fetch</button>
</div>

In this configuration, only hx-target is inherited by the button. Without the hx-inherit declaration, getAttributeValueWithDisinheritance returns null, and the attribute value is not passed down.

To enable inheritance for all attributes on a specific element, use the wildcard:

<div hx-target="#out" hx-select="#content" hx-inherit="*">
  <button hx-get="/msg">Fetch</button>
</div>

The wildcard * allows every htmx attribute defined on that ancestor to cascade to descendants, effectively simulating default behavior for that subtree while maintaining strict isolation elsewhere in the application.

Working Code Examples

Example 1: Selective Opt-Out with hx-disinherit

Prevent a parent swap strategy from affecting a specific button while keeping other inherited attributes:

<div hx-target="#modal" hx-swap="innerHTML" hx-disinherit="hx-swap">
  <button hx-get="/alert">Inherits target, not swap</button>
  <button hx-get="/banner" hx-swap="outerHTML">Custom swap</button>
</div>

The first button inherits hx-target="#modal" but uses the default swap behavior rather than innerHTML.

Example 2: Global Lockdown with Selective Opt-In

Disable inheritance globally, then enable it only where needed:

<script>
  htmx.config.disableInheritance = true;
</script>

<div hx-target="#default" hx-inherit="hx-target">
  <button hx-get="/page">Uses #default target</button>
</div>

<div hx-target="#other">
  <button hx-get="/other">Does not inherit target</button>
</div>

The first button inherits the target because its parent declares hx-inherit="hx-target". The second div's target is not inherited by its button, which must specify its own target or use htmx defaults.

Example 3: Wildcard Inheritance Control

Use * to manage inheritance scopes efficiently:

<!-- Block all inheritance from this container -->
<section hx-disinherit="*">
  <div hx-target="#local-only">
    <button hx-get="/data">Only sees #local-only if defined on this div</button>
  </div>
</section>

<!-- Enable all inheritance when globally disabled -->
<div hx-inherit="*" hx-target="#global" hx-select="#content" hx-swap="beforeend">
  <button hx-get="/append">Inherits target, select, and swap</button>
</div>

Summary

  • Default behavior: Attributes automatically cascade from ancestors to descendants through the getClosestAttributeValue traversal in src/htmx.js.
  • hx-disinherit: Blocks inheritance for specified attributes (or all with *) when global inheritance is enabled, returning the sentinel 'unset' value.
  • hx-inherit: Enables inheritance for specific attributes (or all with *) only when htmx.config.disableInheritance is true.
  • Implementation: The getAttributeValueWithDisinheritance function (lines 466–482) checks both directives and the global configuration to determine inheritance eligibility.
  • Sentinel value: The string 'unset' tells htmx to ignore an ancestor's value and continue searching up the DOM tree.
  • Testing: Repository tests in test/attributes/hx-inherit.js and test/attributes/hx-disinherit.js verify these behaviors.

Frequently Asked Questions

How do I prevent all htmx attributes from being inherited by child elements?

Use hx-disinherit="*" on the parent element. According to the source code in src/htmx.js, this wildcard tells getAttributeValueWithDisinheritance to return 'unset' for every attribute lookup, effectively isolating the subtree from parent configuration while maintaining the default inheritance mode for the rest of the application.

Why does hx-inherit not work when htmx.config.disableInheritance is false?

hx-inherit is designed specifically as an opt-in mechanism for restricted environments. When disableInheritance is false (the default), inheritance already occurs automatically, making hx-inherit redundant. The function getAttributeValueWithDisinheritance only evaluates the hx-inherit variable when the global configuration flag is set to true.

What happens when both hx-inherit and hx-disinherit are present on the same element?

The source code evaluates these attributes in mutually exclusive branches based on the htmx.config.disableInheritance flag. If global inheritance is disabled, only hx-inherit is processed; if enabled, only hx-disinherit is processed. They do not conflict because they operate in different configuration modes, though colocating them would create confusion and is not recommended.

Can I inherit some attributes while blocking others in the same component hierarchy?

Yes. In default mode, use hx-disinherit="attr1 attr2" to block specific attributes while allowing others to cascade. In restricted mode (with htmx.config.disableInheritance = true), use hx-inherit="attr1 attr2" to allow only those specific attributes to be inherited. Both attributes accept space-separated lists to fine-tune which configuration values cross component boundaries.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →