Drupal has long offered multiple ways to build dynamic user interfaces: Form API AJAX, BigPipe, and fully decoupled frontends with React or Vue. Each approach solved specific problems, but often at the cost of complexity, heavy JavaScript, or difficult maintenance.
With Drupal 11.3, HTMX becomes a first‑class citizen in core, offering a modern alternative: rich, reactive UX built primarily with HTML and server-side rendering, while dramatically reducing JavaScript overhead.
In this article, we’ll look at: - What HTMX is and why it matters for Drupal - What exactly landed in Drupal 11.3 - Practical examples with render arrays, forms, and routes - How HTMX works together with BigPipe
What Is HTMX?
HTMX is a small, dependency-free JavaScript library that allows HTML elements to make HTTP requests and update the DOM using HTML attributes instead of custom JavaScript.
Instead of writing JS like: - event listeners - fetch / XMLHttpRequest - DOM replacement logic
You describe interactions declaratively in HTML.
In Drupal, HTMX attributes are exposed using the data-hx-* syntax to ensure valid HTML.
Core HTMX Concepts
1. Any Element Can Make a Request
HTMX is not limited to <a> or <form> elements. Any element can trigger a request using any HTTP verb.
<button data-hx-get="/demo/replace">Load content</button>Supported attributes:
- - data-hx-get
- - data-hx-post
- - data-hx-put
- - data-hx-patch
- - data-hx-delete
2. Any Event Can Trigger a Request
Requests are not limited to click or submit.
<input
data-hx-get="/search"
data-hx-trigger="keyup changed delay:300ms"
data-hx-target="#results"
/>3. Any Target Can Be Updated
By default, HTMX swaps content into the element that triggered the request. You can explicitly define a target using a CSS selector.
<div id="results"></div>
<button
data-hx-get="/demo/replace"
data-hx-target="#results">
Update results
</button>4. Flexible Swap Strategies
HTMX controls how new HTML is inserted.
data-hx-swap="innerHTML | outerHTML | append | prepend"In Drupal + BigPipe scenarios, outerHTML is usually the safest choice.
HTMX in Drupal 11.3
HTMX was added as a dependency in Drupal 11.2, but Drupal 11.3 introduces full framework-level support.
Key additions:
- Htmx factory class for render arrays
- Native support for HTMX-powered Form API rebuilds
- Optimized HTMX responses (main content only)
- _htmx_route option for routes
Using the Htmx Factory Class
Drupal now provides a dedicated API to build HTMX attributes in PHP.
use Drupal\Core\Render\Htmx;
(new Htmx())
->get('/demo/replace')
->target('#content-wrapper')
->swap('outerHTML')
->applyTo($build['button']);Benefits:
- - Clear, discoverable API
- - No hardcoded attribute strings
- - Better integration with render arrays
Example 1: Simple Partial Replacement
Controller
public function replace(): array {
return [
'#markup' => '<div id="content-wrapper">New content loaded</div>',
];
}Render Array
$build['button'] = [
'#type' => 'html_tag',
'#tag' => 'button',
'#value' => 'Load content',
];
(new Htmx())
->get('/demo/replace')
->target('#content-wrapper')
->swap('outerHTML')
->applyTo($build['button']);No custom JavaScript is required.
Example 2: Dynamic Form Without AJAX API
One of the biggest improvements in Drupal 11.3 is native HTMX support in Form API.
Form Definition
public function buildForm(array $form, FormStateInterface $form_state): array {
$makes = ['Audi', 'Toyota', 'BMW'];
$models = [
['A1', 'A4', 'A6'],
['Land Cruiser', 'Tacoma', 'Yaris'],
['325i', 'X5', 'X7'],
];
$form['make'] = [
'#type' => 'select',
'#title' => 'Make',
'#options' => $makes,
];
$form['model'] = [
'#type' => 'select',
'#title' => 'Model',
'#options' => $models[$form_state->getValue('make', 0)] ?? [],
'#wrapper_attributes' => ['id' => 'models-wrapper'],
];
return $form;
}HTMX Enhancement
(new Htmx())
->post()
->target('#models-wrapper')
->select('#models-wrapper')
->swap('outerHTML')
->applyTo($form['make']);Whenever the user changes the car make, the models select is rebuilt on the server and replaced on the page.
No #ajax, no callbacks, no JavaScript.
Optimized Responses: onlyMainContent()
Drupal 11.3 introduces a specialized renderer that returns only the main content area for HTMX requests.
(new Htmx())
->post()
->onlyMainContent()
->target('#models-wrapper')
->swap('outerHTML')
->applyTo($form['make']);This keeps responses small and fast, while remaining compatible with BigPipe.
HTMX-Specific Routes
Routes that exist solely to serve HTMX requests can enable a special option.
htmx.demo:
path: '/htmx/demo'
defaults:
_controller: '\Drupal\demo\Controller\DemoController::replace'
_title: 'HTMX Demo'
requirements:
_permission: 'access content'
options:
_htmx_route: TRUEThis disables full page rendering and returns only the relevant HTML fragment.
Example 3: Views Filtering and Pager with HTMX
One of the most common use cases for Drupal AJAX is Views filtering and pagination. Drupal 11.3 + HTMX allows you to implement this without the legacy AJAX API and with full BigPipe compatibility.
Scenario
- A View listing products
- Exposed filters (e.g. category)
- Pager (next / previous)
- Only the View should update, not the entire page
Step 1: Wrap the View Output
In a controller, block, or layout, wrap the View in a stable container.
$build['products'] = [
'#type' => 'container',
'#attributes' => ['id' => 'products-view-wrapper'],
'view' => views_embed_view('products', 'page_1'),
];This wrapper is critical. HTMX will always replace it as a whole.
Step 2: Enhance Exposed Filter with HTMX
Assume the exposed filter form is rendered above the View.
use Drupal\Core\Render\Htmx;
(new Htmx())
->get('/products')
->target('#products-view-wrapper')
->select('#products-view-wrapper')
->swap('outerHTML')
->applyTo($form['category']);When the filter value changes:
- HTMX sends a GET request
- Drupal rebuilds the View with exposed input
- Only the View wrapper is replaced
No JavaScript and no Views AJAX settings required.
Step 3: Pager Links (Zero Configuration)
Pager links inside the View are plain <a> elements.
HTMX automatically intercepts clicks inside an HTMX context, so:
- Clicking “Next” sends an HTMX request
- Drupal renders the next page of the View
- The wrapper is replaced
No additional configuration is required.
Step 4: Optimize the Route for HTMX
Define the View page route with the _htmx_route option.
view.products.page_1:
options:
_htmx_route: TRUEThis ensures:
- No full page chrome
- Smaller HTML responses
- Faster swaps
- Full BigPipe support
Why outerHTML Matters for Views
Views often contain:
- Lazy builders
- Blocks
- Contextual filters
Replacing only innerHTML may leave stale BigPipe placeholders.
Best practice:
->swap('outerHTML')Result
You get:
- HTMX-powered filtering
- HTMX-powered pagination
- BigPipe streaming
- Minimal JavaScript
- Server-rendered HTML
All using standard Drupal Views.
HTMX and BigPipe: A Perfect Match
BigPipe streams HTML placeholders and replaces them asynchronously. HTMX works seamlessly with this approach because it:
- Inserts real HTML (not JSON)
- Executes <script> chunks correctly
- Preserves BigPipe placeholders
Best practice:
->swap('outerHTML')This ensures placeholder IDs remain consistent and prevents rendering issues.
When to Use HTMX in Drupal
HTMX is an excellent choice for:
- Forms with dynamic elements
- Views filtering and pagination
- Admin UIs
- Progressive enhancement
- Reducing JavaScript payloads
It is not a replacement for fully decoupled architectures when you need:
- Complex client-side state
- Offline support
- Native mobile applications
Conclusion
With Drupal 11.3, HTMX becomes a powerful new default for building interactive user interfaces:
- Less JavaScript
- Faster perceived performance
- Cleaner server-side logic
- Native integration with Form API and BigPipe
HTMX does not replace headless Drupal—but it significantly expands what can be done without leaving the Drupal rendering model.
For many projects, it represents the best balance between classic multi-page applications and fully decoupled frontends.
Comments