Help us improve
Share bugs, ideas, or general feedback.
From drupal-htmx
Implements HTMX in Drupal 11.3+ for dynamic interactions like dependent dropdowns, infinite scroll, real-time validation, multi-step wizards. Guides AJAX migration.
npx claudepluginhub camoa/claude-skills --plugin drupal-htmxHow this skill is triggered — by the user, by Claude, or both
Slash command
/drupal-htmx:htmx-developmentThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Drupal 11.3+ HTMX implementation and AJAX migration guidance.
Guides Symfony developers via decision tree to select UX tools like Stimulus, Turbo, TwigComponent, LiveComponent for interactive, server-rendered frontends with minimal JS.
Provides patterns for Drupal 11 OOP hooks with #[Hook] attributes, form alters, entity hooks, and legacy bridges for Drupal 10/11. Use when implementing hooks, form alterations, or event subscribers.
Share bugs, ideas, or general feedback.
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