htmx-development

📁 camoa/claude-skills 📅 1 day ago
1
总安装量
1
周安装量
#49174
全站排名
安装命令
npx skills add https://github.com/camoa/claude-skills --skill htmx-development

Agent 安装分布

mcpjam 1
claude-code 1
windsurf 1
zencoder 1
crush 1
cline 1

Skill 文档

HTMX Development

Drupal 11.3+ HTMX implementation and AJAX migration guidance.

When to Use

  • Implementing dynamic content updates in Drupal
  • Building forms with dependent fields
  • Migrating existing AJAX to HTMX
  • Adding infinite scroll, load more, real-time validation
  • NOT for: Traditional AJAX maintenance (use ajax-reference.md)

Decision: HTMX vs AJAX

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.

Quick Start

1. Basic HTMX Element

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']);

2. Controller Returns Render Array

public function content() {
  return ['#markup' => '<p>Content loaded</p>'];
}

3. Route (Optional HTMX-Only)

my.content:
  path: '/my/content'
  options:
    _htmx_route: TRUE  # Always minimal response

Core Patterns

Pattern Selection

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')

Dependent Dropdown

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

Multiple Element Updates

// Primary element updates via target
// Secondary element updates via OOB
(new Htmx())
  ->swapOob('outerHTML:[data-secondary]')
  ->applyTo($form['secondary'], '#wrapper_attributes');

URL History

(new Htmx())
  ->pushUrlHeader(Url::fromRoute('my.route', $params))
  ->applyTo($form);

Htmx Class Reference

Request Methods

  • get(Url) / post(Url) / put(Url) / patch(Url) / delete(Url)

Control Methods

  • target(selector) – Where to swap
  • select(selector) – What to extract from response
  • swap(strategy) – How to swap (outerHTML, innerHTML, beforeend, etc.)
  • swapOob(selector) – Out-of-band updates
  • trigger(event) – When to trigger
  • vals(array) – Additional values
  • onlyMainContent() – Minimal response

Response Headers

  • pushUrlHeader(Url) – Update browser URL
  • redirectHeader(Url) – Full redirect
  • triggerHeader(event) – Fire client event
  • reswapHeader(strategy) – Change swap
  • retargetHeader(selector) – Change target

See: references/quick-reference.md for complete tables

Detecting HTMX Requests

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(); }
}

Migration from AJAX

Quick Conversion

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

Migration Steps

  1. Identify #ajax properties
  2. Replace with Htmx class
  3. Move callback logic to buildForm()
  4. Use getHtmxTriggerName() for conditional logic
  5. Replace AjaxResponse with render arrays
  6. Test progressive enhancement

See: references/migration-patterns.md for detailed examples

Validation Checklist

When reviewing HTMX implementations:

  • Htmx class used (not raw attributes)
  • onlyMainContent() for minimal response
  • Proper swap strategy selected
  • OOB used for multiple updates
  • Trigger element detection works
  • Works without JavaScript (progressive)
  • Accessibility: aria-live for dynamic regions
  • URL updates for bookmarkable states

Common Issues

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

  • references/quick-reference.md – Command equivalents, method tables
  • references/htmx-implementation.md – Full Htmx class API, detection, JS
  • references/migration-patterns.md – 7 patterns with before/after code
  • references/ajax-reference.md – AJAX commands for understanding existing code

Key Files in Drupal Core

  • core/lib/Drupal/Core/Htmx/Htmx.php – Main API
  • core/lib/Drupal/Core/Htmx/HtmxRequestInfoTrait.php – Request detection
  • core/lib/Drupal/Core/Render/MainContent/HtmxRenderer.php – Response renderer
  • core/modules/config/src/Form/ConfigSingleExportForm.php – Production example
  • core/modules/system/tests/modules/test_htmx/ – Test examples