Building HTMX-powered interfaces with ECA¶
HTMX lets you build dynamic, interactive interfaces by
adding attributes to HTML elements, without writing JavaScript. Drupal core
ships HTMX and a small integration layer, and the eca_htmx module exposes
that integration to ECA models. This lets you build auto-refreshing regions,
click-to-load fragments, and dynamic responses entirely from the modeler.
This guide explains what Drupal core provides, which ECA plugins map to those features, and how to combine them into working patterns.
How HTMX works in Drupal¶
HTMX is a declarative markup system. You add data-hx-* attributes to an
element, and the HTMX JavaScript library turns them into AJAX requests that
swap the response into the page. For example, an element with
data-hx-get="/clock" and data-hx-trigger="every 1s" polls /clock once a
second and replaces its own content with the response.
Drupal core provides three building blocks:
- The
core/drupal.htmxlibrary — the HTMX runtime plus Drupal glue that re-attaches Drupal behaviors to swapped-in content and loads any new CSS and JavaScript the response requires. - The minimal response renderer — when a request is marked as an HTMX
request, core returns only the page's main content wrapped in a bare HTML
document instead of the full themed page (header, blocks, and footer are
omitted). The minimal response also carries an
X-Robots-Tag: noindexheader so the fragment is not indexed by search engines. - HTMX request and response headers — HTMX sends
HX-*request headers (such asHX-RequestandHX-Trigger) and honorsHX-*response headers (such asHX-RedirectandHX-Trigger) to coordinate client behavior.
The eca_htmx module builds entirely on these core building blocks. It never
ships its own HTMX JavaScript, and it produces all attributes and headers
through core's HTMX helper, so the markup stays consistent with the rest of
Drupal.
The two ways to request a minimal response¶
A recurring source of confusion is that core offers two independent ways to get the minimal (main-content-only) response, and they take effect at different stages of the request lifecycle. They are not interchangeable.
_htmx_routeroute option- Set on a route while routes are being built. Any request to a route carrying this option returns the minimal response. Use the HTMX: mark route action, which reacts to the Alter route event.
_wrapper_format=drupal_htmxquery parameter- Added to an individual request at runtime. Only that request returns the minimal response. Use the HTMX: minimal response action, or let the polling and element actions add it automatically through their only main content option.
Which one to use
Mark the route when you control the route and want every request to it to return a fragment (for example, a dedicated endpoint that only ever serves a fragment). Mark the request when you want a specific interaction to fetch a fragment from a route that otherwise renders a full page.
The plugins¶
Building HTMX elements¶
- HTMX: render element
- Builds a single element carrying the HTMX attributes you configure: the request method and URL, the trigger, the swap strategy, the target and select selectors, and options such as a confirmation dialog, indicators, pushed URLs, and request values. Use this for click-to-load fragments, infinite scroll, and similar interactions.
- HTMX: poll region
- A specialized element that polls a URL on an interval and swaps in the response. Use it for content that refreshes on its own, such as a clock, a status indicator, or a notification count. The only main content option is enabled by default, so the polled URL automatically returns a fragment.
Both actions render an element. Place them in a block, an entity display, a
form, or any other render context, and they attach core/drupal.htmx
automatically.
Reacting to HTMX requests¶
- HTMX: is request
- A condition that is true when the current request is an HTMX request. Use it to branch a model so that it builds a fragment for HTMX requests and a full page otherwise.
- HTMX: compare request value
- A condition that compares a value from the current HTMX request — such as the target element, the trigger, or the current URL — against an expected value.
The same request information is available as tokens, so you can read the HTMX target or trigger anywhere a token is accepted.
Identifying the source and the request type¶
As of ECA 3.1.x, two further request values are available, each of them both as a token and as an option on the HTMX: compare request value condition:
| Token | Condition value option | Header | Contains |
|---|---|---|---|
[htmx:source] |
source |
HX-Source |
The CSS selector of the element that triggered the request, for example button[name="first_item"] |
[htmx:request_type] |
request_type |
HX-Request-Type |
Whether the request targets a full page or a fragment: full or partial |
[htmx:request_type] lets a model branch on a full page load versus a fragment
swap, which no token made it possible to determine before. [htmx:source]
identifies the triggering element more specifically than [htmx:trigger], which
falls short when more than one element shares an id, or when the element carries
none at all. The existing HTMX tokens are unchanged.
Both headers need Drupal 12
HX-Source and HX-Request-Type are sent by HTMX 4, which ships with
Drupal 12. On Drupal 11, which ships HTMX 3, neither header is sent and
both tokens resolve to an empty string.
That emptiness is deliberate. On Drupal 11 the tokens stay empty even when an
HX-Source header is present on the request: HTMX 3 never sends one, so
such a value reached the site from somewhere else, such as a proxy, and must
not be trusted.
If your site may run on Drupal 11, write models that cope with an empty value.
Shaping HTMX responses¶
- HTMX: set response header
- Sets any of the HTMX
HX-*response headers on the current response, for exampleHX-Redirectto redirect the client,HX-Triggerto fire a client-side event, orHX-Retargetto change where the response is swapped. TheHX-Locationheader supports its full form (source, event, handler, target, swap, values, headers, and select).
Because this action shapes the response, it reacts to a response event such as the Response created event or an ECA endpoint response.
Rendering HTMX elements into a form¶
A Drupal form is a render array, and ECA's form events expose it as one. The
Build form and
Process form events hand out
the form array itself as the event's render array, so both HTMX actions, and
every eca_render element action, work inside a form exactly as they work
inside a block. No configuration differs.
That opens up patterns a form otherwise needs custom code for:
- A polling region inside a form, for example a live lock or status indicator.
- A click-to-load element that swaps a fragment into a container that already exists on the page.
- An element that reacts to a change on an existing form field, using the HTMX
from:trigger modifier, such aschange from:#edit-field-category.
Three limits apply, and they are easier to learn here than by trial:
- The request carries no form values. The element has no
hx-includeoption, and Values resolves its tokens when the form is built rather than when the request is issued, so the fragment cannot depend on what the user has typed. - Target and Indicator take a CSS selector, and the element itself
gets neither an
idnor a class. Only selectors that already exist on the page can be addressed. - Wrapper tag
buttonproduces a<button>with notypeattribute, which HTML treats astype="submit". Inside a form, clicking it submits the form. Useaorspaninstead.
The Machine name is a form element key in this context, and ][ nesting
addresses nested form elements, so details][title places the element inside
an existing details element.
Leave the machine name set
With Build mode set to Set and clear any previous value and no machine name, the action replaces the event's entire render array. Under a form event, that is the whole form. Use Set value when machine name is defined above when a token supplies the name and might resolve to nothing.
Serving fragments with ECA endpoints¶
The natural source for an HTMX fragment is an ECA endpoint. An endpoint gives you a URL that an ECA model fully controls: the model builds the response content and can attach HTMX response headers. It pairs naturally with a polling region or a click-to-load element that points at it.
When the "only main content" option matters¶
The only main content option adds Drupal's _wrapper_format=drupal_htmx
hint to the request, which asks Drupal to return the page's main content instead
of the full themed page. Against an ECA endpoint, whether that hint changes
anything depends on how the model produces the response:
- The model sets the response content, for example with Set response content. The endpoint returns that response unchanged and the hint has no effect, so the option makes no difference either way.
- The model only builds a render array and sets no response content. The endpoint hands the render array back to Drupal's page pipeline, which themes it as a full page unless the request carries the hint. Here the option is required.
Against a route that is not an ECA endpoint, such as /node/1, the hint is
what you want, because the route renders a full themed page.
The poll action enables the option by default, the element action does not. Set it from the URL you point at rather than from the default.
Common patterns¶
Auto-refreshing region¶
- Event: any event that builds a render element, such as a block build.
- Action: HTMX: poll region — set the URL to an endpoint that returns the fragment, and the interval in seconds.
The region polls the endpoint and swaps in the response on every interval. Leave only main content enabled, which is the default: it is what makes the endpoint return a fragment when the model builds the response as a render array, and it costs nothing when the model sets the response content directly.
Click-to-load content¶
- Event: any event that builds a render element.
- Action: HTMX: render element —
set the method to
GET, the URL to the fragment endpoint, the trigger toclick, and the target to the element that should receive the content.
Serve a fragment from an endpoint¶
- Event: ECA endpoint response — define the endpoint path.
- Action: Set response content — build the fragment markup.
- Action (optional):
HTMX: set response header —
fire a client-side event with
HX-Trigger, for example to notify other parts of the page that the fragment refreshed.
Turn an existing route into a fragment¶
- Event: Alter route.
- Action: HTMX: mark route —
set the route name (a
*wildcard is supported) to mark as an HTMX route.
Every request to a marked route then returns the minimal response.
Related resources¶
- HTMX: render element — action reference
- HTMX: poll region — action reference
- HTMX: mark route — action reference
- HTMX: minimal response — action reference
- HTMX: set response header — action reference
- HTMX: is request — condition reference
- HTMX: compare request value — condition reference
- Choosing the right request and response events — guide
- HTMX Blocks — library example