Claude-skill-registry htmx

Adds AJAX, CSS Transitions, WebSockets, and Server Sent Events to HTML using attributes. Use when building hypermedia-driven applications, adding interactivity without JavaScript frameworks, or when user mentions HTMX, hx-boost, or HTML-first development.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/htmx" ~/.claude/skills/majiayu000-claude-skill-registry-htmx && rm -rf "$T"
manifest: skills/data/htmx/SKILL.md
source content

HTMX

High-power tools for HTML - access AJAX, CSS Transitions, WebSockets and Server Sent Events directly from HTML.

Quick Start

<!-- Include htmx -->
<script src="https://unpkg.com/htmx.org@2.0.8"></script>

<!-- Basic AJAX request -->
<button hx-get="/api/data" hx-target="#result">
  Load Data
</button>
<div id="result"></div>

Core Attributes

Request Types

<!-- GET request -->
<button hx-get="/api/users">Load Users</button>

<!-- POST request -->
<form hx-post="/api/users">
  <input name="name" required>
  <button type="submit">Create User</button>
</form>

<!-- PUT request -->
<button hx-put="/api/users/123" hx-vals='{"name": "Updated"}'>
  Update User
</button>

<!-- PATCH request -->
<button hx-patch="/api/users/123" hx-vals='{"status": "active"}'>
  Activate User
</button>

<!-- DELETE request -->
<button hx-delete="/api/users/123" hx-confirm="Are you sure?">
  Delete User
</button>

Targeting

<!-- Target by ID -->
<button hx-get="/content" hx-target="#container">Load</button>
<div id="container"></div>

<!-- Target closest parent matching selector -->
<button hx-get="/row" hx-target="closest tr">Update Row</button>

<!-- Target next sibling -->
<input hx-get="/search" hx-target="next .results" hx-trigger="keyup">
<div class="results"></div>

<!-- Target previous sibling -->
<button hx-get="/status" hx-target="previous .status">Check</button>

<!-- Target find within -->
<div hx-get="/content" hx-target="find .inner">
  <div class="inner"></div>
</div>

<!-- Target this element -->
<button hx-get="/self-update" hx-target="this">
  Click to Update
</button>

Swap Strategies

<!-- Replace inner HTML (default) -->
<div hx-get="/content" hx-swap="innerHTML">Loading...</div>

<!-- Replace entire element -->
<div hx-get="/content" hx-swap="outerHTML">Replace me</div>

<!-- Insert before element -->
<ul hx-get="/item" hx-swap="beforebegin">
  <li>Existing item</li>
</ul>

<!-- Insert after element -->
<ul hx-get="/item" hx-swap="afterend">
  <li>Existing item</li>
</ul>

<!-- Prepend to element -->
<ul hx-get="/item" hx-swap="afterbegin">
  <li>Existing item</li>
</ul>

<!-- Append to element -->
<ul hx-get="/item" hx-swap="beforeend" hx-trigger="click from:button">
  <li>Existing item</li>
</ul>

<!-- Delete target element -->
<button hx-delete="/item/1" hx-target="closest li" hx-swap="delete">
  Remove
</button>

<!-- No swap, just trigger events -->
<button hx-post="/action" hx-swap="none">
  Trigger Action
</button>

Swap Modifiers

<!-- Swap with transition delay -->
<div hx-get="/content" hx-swap="innerHTML swap:500ms">
  Fades out, waits 500ms, then swaps
</div>

<!-- Settle delay (after swap) -->
<div hx-get="/content" hx-swap="innerHTML settle:300ms">
  Swaps, then waits 300ms before settling
</div>

<!-- Scroll to element -->
<div hx-get="/content" hx-swap="innerHTML scroll:top">
  Scrolls to top after swap
</div>

<!-- Show element in viewport -->
<div hx-get="/content" hx-swap="innerHTML show:bottom">
  Shows bottom of element
</div>

<!-- Focus scroll control -->
<div hx-get="/content" hx-swap="innerHTML focus-scroll:false">
  Prevents scroll to focused input
</div>

<!-- View Transitions API -->
<div hx-get="/content" hx-swap="innerHTML transition:true">
  Uses View Transitions API
</div>

Triggers

Event Triggers

<!-- Click (default for buttons) -->
<button hx-get="/data" hx-trigger="click">Click Me</button>

<!-- Change (default for inputs) -->
<select hx-get="/filter" hx-trigger="change">
  <option value="1">Option 1</option>
</select>

<!-- Submit (default for forms) -->
<form hx-post="/submit" hx-trigger="submit">
  <button type="submit">Submit</button>
</form>

<!-- Keyboard events -->
<input hx-get="/search" hx-trigger="keyup">

<!-- Mouse events -->
<div hx-get="/preview" hx-trigger="mouseenter">
  Hover for preview
</div>

<!-- Custom events -->
<div hx-get="/data" hx-trigger="customEvent from:body">
  Listens for custom event
</div>

Trigger Modifiers

<!-- Delay trigger -->
<input hx-get="/search" hx-trigger="keyup delay:500ms">

<!-- Throttle trigger -->
<div hx-get="/position" hx-trigger="mousemove throttle:100ms">
  Track mouse
</div>

<!-- Only once -->
<button hx-get="/init" hx-trigger="click once">
  Initialize (only works once)
</button>

<!-- Changed filter (only if value changed) -->
<input hx-get="/search" hx-trigger="keyup changed delay:300ms">

<!-- From another element -->
<input id="search" type="text">
<div hx-get="/results" hx-trigger="keyup from:#search delay:300ms">
  Results appear here
</div>

<!-- Multiple triggers -->
<div hx-get="/data" hx-trigger="click, keyup from:body[key='Enter']">
  Multiple triggers
</div>

<!-- Polling -->
<div hx-get="/status" hx-trigger="every 5s">
  Auto-refreshes every 5 seconds
</div>

<!-- Load on page load -->
<div hx-get="/initial" hx-trigger="load">
  Loads on page load
</div>

<!-- Load when revealed (lazy loading) -->
<div hx-get="/lazy" hx-trigger="revealed">
  Loads when scrolled into view
</div>

<!-- Intersect (Intersection Observer) -->
<div hx-get="/more" hx-trigger="intersect threshold:0.5">
  Loads when 50% visible
</div>

hx-boost

<!-- Boost all links and forms in a container -->
<div hx-boost="true">
  <!-- These become AJAX requests -->
  <a href="/page1">Page 1</a>
  <a href="/page2">Page 2</a>

  <form action="/submit" method="post">
    <input name="data">
    <button type="submit">Submit</button>
  </form>

  <!-- Opt out specific elements -->
  <a href="/external" hx-boost="false">External Link</a>
</div>

<!-- Boost with target -->
<body hx-boost="true" hx-target="#main" hx-swap="innerHTML">
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>
  <main id="main">
    <!-- Content swaps here -->
  </main>
</body>

Form Handling

Basic Forms

<!-- Standard form -->
<form hx-post="/api/users" hx-target="#result">
  <input name="name" required>
  <input name="email" type="email" required>
  <button type="submit">Create</button>
</form>
<div id="result"></div>

<!-- Form with JSON encoding -->
<form hx-post="/api/users" hx-ext="json-enc" hx-target="#result">
  <input name="name">
  <button type="submit">Create</button>
</form>

Including Values

<!-- Include values from other elements -->
<input id="filter" name="filter" value="active">
<button hx-get="/users" hx-include="#filter">
  Filter Users
</button>

<!-- Include closest form -->
<form>
  <input name="query">
  <button hx-get="/search" hx-include="closest form">
    Search
  </button>
</form>

<!-- Include all inputs in parent -->
<div>
  <input name="a" value="1">
  <input name="b" value="2">
  <button hx-get="/data" hx-include="this">
    Include both inputs
  </button>
</div>

<!-- Add inline values -->
<button hx-post="/action" hx-vals='{"key": "value", "count": 42}'>
  With Values
</button>

<!-- Dynamic values with JavaScript -->
<button hx-post="/action" hx-vals='js:{timestamp: Date.now()}'>
  Dynamic Value
</button>

Form Validation

<form hx-post="/api/register"
      hx-target="#result"
      hx-indicator=".spinner">

  <label>
    Email
    <input name="email" type="email" required
           hx-get="/api/validate-email"
           hx-trigger="blur"
           hx-target="next .error">
    <span class="error"></span>
  </label>

  <label>
    Password
    <input name="password" type="password" required minlength="8">
  </label>

  <button type="submit">Register</button>
  <span class="spinner htmx-indicator">Loading...</span>
</form>
<div id="result"></div>

Out-of-Band Swaps

<!-- Server can return multiple elements to swap -->
<!-- Response from server: -->
<div id="main-content">
  Main content here
</div>

<div id="notification" hx-swap-oob="true">
  Notification updated!
</div>

<div id="sidebar" hx-swap-oob="innerHTML">
  Sidebar content updated
</div>

<!-- Swap strategy in oob -->
<tr id="row-5" hx-swap-oob="outerHTML">
  Updated row content
</tr>

Indicators

<!-- Show spinner during request -->
<button hx-get="/slow"
        hx-indicator="#spinner">
  Load Data
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>

<!-- Indicator on parent -->
<div hx-indicator=".spinner">
  <button hx-get="/data">Load</button>
  <span class="spinner htmx-indicator">...</span>
</div>

<style>
  /* Default htmx indicator styles */
  .htmx-indicator {
    opacity: 0;
    transition: opacity 200ms ease-in;
  }
  .htmx-request .htmx-indicator {
    opacity: 1;
  }
  .htmx-request.htmx-indicator {
    opacity: 1;
  }
</style>

CSS Transitions

<!-- Swapping classes for transitions -->
<style>
  .fade-me-in.htmx-added {
    opacity: 0;
  }
  .fade-me-in {
    opacity: 1;
    transition: opacity 1s ease-out;
  }
</style>

<button hx-get="/content" hx-swap="innerHTML" hx-target="#container">
  Load
</button>
<div id="container">
  <!-- Content with class="fade-me-in" will animate -->
</div>

<!-- Settling classes -->
<style>
  .my-content.htmx-settling {
    opacity: 0;
  }
  .my-content {
    transition: opacity 0.5s ease-in;
  }
</style>

WebSockets

<!-- Connect to WebSocket -->
<div hx-ext="ws" ws-connect="/ws/chat">
  <div id="messages">
    <!-- Messages appear here via ws-send -->
  </div>

  <form ws-send>
    <input name="message">
    <button type="submit">Send</button>
  </form>
</div>

Server-Sent Events

<!-- Connect to SSE endpoint -->
<div hx-ext="sse" sse-connect="/events">
  <!-- Swap content on specific event -->
  <div sse-swap="message">
    Waiting for messages...
  </div>

  <!-- Trigger htmx request on event -->
  <div hx-get="/data" hx-trigger="sse:update">
    Refreshes on 'update' event
  </div>
</div>

Request Headers

<!-- Add custom headers -->
<button hx-get="/api/data"
        hx-headers='{"X-Custom-Header": "value"}'>
  With Headers
</button>

<!-- HTMX automatically sends these headers: -->
<!-- HX-Request: true -->
<!-- HX-Trigger: <element id> -->
<!-- HX-Trigger-Name: <element name> -->
<!-- HX-Target: <target element id> -->
<!-- HX-Current-URL: <current url> -->

Response Headers

# Server can control htmx behavior via headers

# Redirect
response.headers['HX-Redirect'] = '/new-page'

# Refresh page
response.headers['HX-Refresh'] = 'true'

# Push URL to history
response.headers['HX-Push-Url'] = '/new-url'

# Replace URL without history
response.headers['HX-Replace-Url'] = '/new-url'

# Retarget the swap
response.headers['HX-Retarget'] = '#other-element'

# Change swap strategy
response.headers['HX-Reswap'] = 'outerHTML'

# Trigger client-side events
response.headers['HX-Trigger'] = 'myEvent'
response.headers['HX-Trigger'] = '{"myEvent": {"key": "value"}}'

# Trigger after settle
response.headers['HX-Trigger-After-Settle'] = 'settled'

# Trigger after swap
response.headers['HX-Trigger-After-Swap'] = 'swapped'

Events

// Listen for htmx events
document.body.addEventListener('htmx:beforeRequest', function(evt) {
  console.log('Request starting:', evt.detail);
});

document.body.addEventListener('htmx:afterSwap', function(evt) {
  console.log('Swap complete:', evt.detail);
});

document.body.addEventListener('htmx:responseError', function(evt) {
  console.error('Request failed:', evt.detail);
});

// Common events:
// htmx:configRequest - modify request before sending
// htmx:beforeRequest - request about to be made
// htmx:afterRequest - request completed
// htmx:beforeSwap - about to swap content
// htmx:afterSwap - swap completed
// htmx:afterSettle - settling completed
// htmx:responseError - non-2xx response
// htmx:sendError - network error

Configuration

<head>
  <meta name="htmx-config" content='{
    "defaultSwapStyle": "outerHTML",
    "defaultSettleDelay": 100,
    "historyCacheSize": 20,
    "scrollBehavior": "smooth",
    "allowEval": false,
    "globalViewTransitions": true
  }'>
</head>

<script>
  // Or configure via JavaScript
  htmx.config.defaultSwapStyle = 'outerHTML';
  htmx.config.useTemplateFragments = true;
</script>

Extensions

<!-- Load extensions -->
<script src="https://unpkg.com/htmx-ext-json-enc@2.0.0/json-enc.js"></script>
<script src="https://unpkg.com/htmx-ext-loading-states@2.0.0/loading-states.js"></script>

<!-- Use extensions -->
<form hx-ext="json-enc" hx-post="/api/data">
  <input name="field">
  <button>Submit as JSON</button>
</form>

<!-- Loading states -->
<button hx-get="/slow"
        hx-ext="loading-states"
        data-loading-class="is-loading"
        data-loading-disable>
  Click Me
</button>

Preserve Elements

<!-- Preserve element across swaps -->
<div hx-preserve="true" id="video-player">
  <!-- Won't be replaced when parent swaps -->
  <video src="/video.mp4" autoplay></video>
</div>

Reference Files