HTMX Patterns Referenz

Erstellt: 2025-12-27 | Aktualisiert: 2025-12-27

Kopierbare Patterns für häufige Anwendungsfälle.

CRUD-Operationen

Create (POST)

<form hx-post="/api/items"
      hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
      hx-target="#form-message"
      hx-swap="innerHTML"
      hx-indicator="#create-btn"
      hx-disabled-elt="#create-btn">
    
    <input type="text" name="title" required>
    <textarea name="description"></textarea>
    
    <button type="submit" id="create-btn" class="btn btn--primary">
        <span class="htmx-content">Erstellen</span>
        <span class="htmx-indicator">Erstelle...</span>
    </button>
</form>
<div id="form-message"></div>

Read (GET mit Partial)

<div hx-get="/api/items/<?= $id ?>/details"
     hx-trigger="revealed"
     hx-swap="innerHTML">
    Lade Details...
</div>

Update (PUT)

<form hx-put="/api/items/<?= $id ?>"
      hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
      hx-target="#form-message"
      hx-swap="innerHTML"
      hx-indicator="#save-btn"
      hx-disabled-elt="#save-btn">
    
    <input type="text" name="title" value="<?= htmlspecialchars($item['title']) ?>">
    
    <button type="submit" id="save-btn" class="btn btn--primary">
        <span class="htmx-content">Speichern</span>
        <span class="htmx-indicator">Speichere...</span>
    </button>
</form>
<div id="form-message"></div>

Delete (DELETE)

<button hx-delete="/api/items/<?= $id ?>"
        hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
        hx-confirm="'<?= htmlspecialchars($item['title']) ?>' wirklich löschen?"
        hx-target="closest tr"
        hx-swap="outerHTML swap:0.3s"
        class="btn btn--danger">
    Löschen
</button>

Inline-Editing

Text-Input mit Auto-Save

<input type="text" 
       name="title"
       value="<?= htmlspecialchars($value) ?>"
       class="inline-edit"
       hx-post="/api/items/<?= $id ?>/title"
       hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
       hx-trigger="blur changed, keyup[key=='Enter'] changed"
       hx-swap="none"
       hx-disabled-elt="this"
       hx-on::after-request="this.classList.toggle('is-saved', event.detail.successful); setTimeout(() => this.classList.remove('is-saved'), 1000)">

Select mit Auto-Save

<select name="status"
        class="inline-select"
        hx-post="/api/items/<?= $id ?>/status"
        hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
        hx-swap="none"
        hx-disabled-elt="this"
        hx-on::after-request="this.classList.toggle('is-saved', event.detail.successful); setTimeout(() => this.classList.remove('is-saved'), 1000)">
    <?php foreach ($statuses as $key => $label): ?>
    <option value="<?= $key ?>" <?= $item['status'] === $key ? 'selected' : '' ?>>
        <?= htmlspecialchars($label) ?>
    </option>
    <?php endforeach; ?>
</select>

Toggle (Checkbox)

<input type="checkbox"
       name="is_active"
       <?= $item['is_active'] ? 'checked' : '' ?>
       hx-post="/api/items/<?= $id ?>/toggle-active"
       hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
       hx-swap="none"
       hx-disabled-elt="this">

Listen & Tabellen

Lazy-Load Tabelle

<table>
    <thead>...</thead>
    <tbody hx-get="/api/items?page=1"
           hx-trigger="revealed"
           hx-swap="innerHTML">
        <tr><td colspan="5">Lade Daten...</td></tr>
    </tbody>
</table>

Infinite Scroll

<div id="items-list">
    <?php foreach ($items as $item): ?>
    <div class="item"><?= htmlspecialchars($item['title']) ?></div>
    <?php endforeach; ?>
    
    <?php if ($hasMore): ?>
    <div hx-get="/api/items?page=<?= $page + 1 ?>"
         hx-trigger="revealed"
         hx-swap="outerHTML"
         hx-indicator="#load-more-spinner">
        <span id="load-more-spinner" class="htmx-indicator">Lade mehr...</span>
    </div>
    <?php endif; ?>
</div>

Sortierbare Spalten

<th hx-get="/api/items?sort=title&dir=<?= $sortDir === 'asc' ? 'desc' : 'asc' ?>"
    hx-target="#items-tbody"
    hx-swap="innerHTML"
    class="sortable <?= $sortCol === 'title' ? 'sorted-' . $sortDir : '' ?>">
    Titel
</th>

Suche & Filter

Live-Search

<input type="search"
       name="q"
       placeholder="Suchen..."
       hx-get="/api/items/search"
       hx-trigger="input changed delay:300ms, search"
       hx-target="#search-results"
       hx-swap="innerHTML"
       hx-indicator="#search-spinner">
<span id="search-spinner" class="htmx-indicator">...</span>
<div id="search-results"></div>

Filter-Form

<form hx-get="/api/items"
      hx-trigger="change from:select, change from:input[type='checkbox']"
      hx-target="#items-list"
      hx-swap="innerHTML"
      hx-indicator="#filter-spinner">
    
    <select name="status">
        <option value="">Alle Status</option>
        <option value="active">Aktiv</option>
        <option value="draft">Entwurf</option>
    </select>
    
    <select name="category">
        <option value="">Alle Kategorien</option>
    </select>
    
    <span id="filter-spinner" class="htmx-indicator">Filtere...</span>
</form>

Modals & Dialogs

Modal laden

<button hx-get="/api/items/<?= $id ?>/edit-modal"
        hx-target="#modal-container"
        hx-swap="innerHTML"
        hx-on::after-request="document.getElementById('modal-container').showModal()">
    Bearbeiten
</button>

<dialog id="modal-container"></dialog>

Modal-Inhalt (Partial)

<form hx-put="/api/items/<?= $id ?>"
      hx-headers='{"X-CSRF-TOKEN": "<?= $csrfToken ?>"}'
      hx-target="#modal-container"
      hx-swap="innerHTML"
      hx-on::after-request="if(event.detail.successful) document.getElementById('modal-container').close()">
    
    <h2>Bearbeiten</h2>
    <input type="text" name="title" value="<?= htmlspecialchars($item['title']) ?>">
    
    <button type="submit">Speichern</button>
    <button type="button" onclick="this.closest('dialog').close()">Abbrechen</button>
</form>

Polling & Updates

Auto-Refresh

<div hx-get="/api/status"
     hx-trigger="every 5s"
     hx-swap="innerHTML">
    Status: <?= $status ?>
</div>

Polling mit Stop-Condition

<div hx-get="/api/jobs/<?= $jobId ?>/status"
     hx-trigger="<?= $job['status'] === 'running' ? 'every 2s' : 'none' ?>"
     hx-swap="outerHTML">
    Status: <?= $job['status'] ?>
    <?php if ($job['status'] === 'running'): ?>
    <span class="spinner"></span>
    <?php endif; ?>
</div>

Verwandte Themen