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.
Adds AJAX, WebSockets, and Server-Sent Events to HTML using attributes like `hx-get` and `hx-target`. Use when building hypermedia-driven applications or adding interactivity without JavaScript frameworks, triggered by mentions of HTMX, `hx-boost`, or HTML-first development.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/patterns.mdHigh-power tools for HTML - access AJAX, CSS Transitions, WebSockets and Server Sent Events directly from HTML.
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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> -->
# 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'
// 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
<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>
<!-- 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 element across swaps -->
<div hx-preserve="true" id="video-player">
<!-- Won't be replaced when parent swaps -->
<video src="/video.mp4" autoplay></video>
</div>
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.