Implements HTMX in Drupal 11.3+ for dynamic interactions like dependent dropdowns, infinite scroll, real-time validation, multi-step wizards. Guides AJAX migration.
From drupal-htmxnpx claudepluginhub camoa/claude-skills --plugin drupal-htmxThis skill is limited to using the following tools:
references/ajax-reference.mdreferences/htmx-implementation.mdreferences/migration-patterns.mdreferences/quick-reference.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Drupal 11.3+ HTMX implementation and AJAX migration guidance.
| Choose HTMX | Choose AJAX |
|---|---|
| New features | Existing AJAX code |
| Declarative HTML preferred | Complex command sequences |
| Returns HTML fragments | Dialog commands needed |
| Progressive enhancement needed | Contrib expects AJAX |
Hybrid OK: Both systems coexist. Migrate incrementally.
use Drupal\Core\Htmx\Htmx;
use Drupal\Core\Url;
$build['button'] = [
'#type' => 'button',
'#value' => t('Load'),
];
(new Htmx())
->get(Url::fromRoute('my.content'))
->onlyMainContent()
->target('#result')
->swap('innerHTML')
->applyTo($build['button']);
public function content() {
return ['#markup' => '<p>Content loaded</p>'];
}
my.content:
path: '/my/content'
options:
_htmx_route: TRUE # Always minimal response
| Use Case | Pattern | Key Methods |
|---|---|---|
| Dependent dropdown | Form partial update | select(), target(), swap('outerHTML') |
| Load more | Append content | swap('beforeend'), trigger('click') |
| Infinite scroll | Auto-load | swap('beforeend'), trigger('revealed') |
| Real-time validation | Blur check | trigger('focusout'), field update |
| Multi-step wizard | URL-based steps | pushUrl(), route parameters |
| Multiple updates | OOB swap | swapOob('outerHTML:#selector') |
public function buildForm(array $form, FormStateInterface $form_state) {
$form['category'] = ['#type' => 'select', '#options' => $this->getCategories()];
(new Htmx())
->post(Url::fromRoute('<current>'))
->onlyMainContent()
->select('#edit-subcategory--wrapper')
->target('#edit-subcategory--wrapper')
->swap('outerHTML')
->applyTo($form['category']);
$form['subcategory'] = ['#type' => 'select', '#options' => []];
// Handle trigger
if ($this->getHtmxTriggerName() === 'category') {
$form['subcategory']['#options'] = $this->getSubcategories(
$form_state->getValue('category')
);
}
return $form;
}
Reference: core/modules/config/src/Form/ConfigSingleExportForm.php
// Primary element updates via target
// Secondary element updates via OOB
(new Htmx())
->swapOob('outerHTML:[data-secondary]')
->applyTo($form['secondary'], '#wrapper_attributes');
(new Htmx())
->pushUrlHeader(Url::fromRoute('my.route', $params))
->applyTo($form);
get(Url) / post(Url) / put(Url) / patch(Url) / delete(Url)target(selector) - Where to swapselect(selector) - What to extract from responseswap(strategy) - How to swap (outerHTML, innerHTML, beforeend, etc.)swapOob(selector) - Out-of-band updatestrigger(event) - When to triggervals(array) - Additional valuesonlyMainContent() - Minimal responsepushUrlHeader(Url) - Update browser URLredirectHeader(Url) - Full redirecttriggerHeader(event) - Fire client eventreswapHeader(strategy) - Change swapretargetHeader(selector) - Change targetSee: references/quick-reference.md for complete tables
In forms (trait included automatically):
if ($this->isHtmxRequest()) {
$trigger = $this->getHtmxTriggerName();
}
In controllers (add trait):
use Drupal\Core\Htmx\HtmxRequestInfoTrait;
class MyController extends ControllerBase {
use HtmxRequestInfoTrait;
protected function getRequest() { return \Drupal::request(); }
}
| AJAX | HTMX |
|---|---|
'#ajax' => ['callback' => '::cb'] | (new Htmx())->post()->applyTo() |
'wrapper' => 'id' | ->target('#id') |
return $form['element'] | Logic in buildForm() |
new AjaxResponse() | Return render array |
ReplaceCommand | ->swap('outerHTML') |
HtmlCommand | ->swap('innerHTML') |
AppendCommand | ->swap('beforeend') |
MessageCommand | Auto-included |
#ajax propertiesHtmx classbuildForm()getHtmxTriggerName() for conditional logicAjaxResponse with render arraysSee: references/migration-patterns.md for detailed examples
When reviewing HTMX implementations:
Htmx class used (not raw attributes)onlyMainContent() for minimal responsearia-live for dynamic regions| Problem | Solution |
|---|---|
| Content not swapping | Check target() selector exists |
| Wrong content extracted | Check select() selector |
| JS not running | Verify htmx:drupal:load fires |
| Form not submitting | Check post() and URL |
| Multiple swaps fail | Add swapOob('true') to elements |
| History broken | Use pushUrlHeader() |
references/quick-reference.md - Command equivalents, method tablesreferences/htmx-implementation.md - Full Htmx class API, detection, JSreferences/migration-patterns.md - 7 patterns with before/after codereferences/ajax-reference.md - AJAX commands for understanding existing codeFor deeper Drupal context beyond bundled references, use the dev-guides-navigator plugin:
Invoke /dev-guides-navigator with keywords like "Drupal forms", "routing", "JS development", or "render API". The navigator handles caching and disambiguation — never fetch dev-guides URLs directly.
Relevant topics: drupal/forms (FAPI with HTMX), drupal/routing (HTMX routes), drupal/js-development (behaviors + HTMX events), drupal/render-api (render arrays for HTMX responses).
core/lib/Drupal/Core/Htmx/Htmx.php - Main APIcore/lib/Drupal/Core/Htmx/HtmxRequestInfoTrait.php - Request detectioncore/lib/Drupal/Core/Render/MainContent/HtmxRenderer.php - Response renderercore/modules/config/src/Form/ConfigSingleExportForm.php - Production examplecore/modules/system/tests/modules/test_htmx/ - Test examples