HTMX Patterns Referenz
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
- HTMX Übersicht - Grundlagen und Konfiguration
- Troubleshooting - Häufige Probleme
- Regeln - HTMX statt fetch() Regel