HTMX WebSockets
WebSockets provide full-duplex communication between the browser and the server — both sides can send data at any time over a single persistent connection. Unlike Server-Sent Events (SSE), which only push data from server to client, WebSockets allow the client to send messages to the server too. HTMX supports WebSockets through an official extension, making it possible to build real-time collaborative and chat features with HTML attributes.
SSE vs WebSockets: When to Use Each
| Feature | Server-Sent Events | WebSockets |
|---|---|---|
| Direction | Server → Client only | Server ↔ Client (both directions) |
| Protocol | HTTP | WS / WSS (own protocol) |
| Reconnection | Automatic | Manual (must handle in code) |
| Best for | Notifications, feeds, dashboards | Chat, collaborative editing, games |
| Complexity | Low | Medium |
Setting Up the WebSocket Extension
<script src="https://unpkg.com/htmx.org@2.0.0"></script> <script src="https://unpkg.com/htmx-ext-ws@2.0.1/ws.js"></script>
Connecting to a WebSocket
<div
hx-ext="ws"
ws-connect="/ws/chat">
<!-- Messages from the server appear here -->
<div id="chat-messages"></div>
<!-- Form sends messages to the server -->
<form ws-send>
<input type="text" name="message" placeholder="Type a message...">
<button type="submit">Send</button>
</form>
</div>
| Attribute | Purpose |
|---|---|
| hx-ext="ws" | Activates the WebSocket extension on this element |
| ws-connect="/url" | The WebSocket endpoint to connect to (ws:// or wss://) |
| ws-send | Placed on a form — sends the form data through the WebSocket when submitted |
How the Message Flow Works
Browser opens WebSocket connection to /ws/chat
User types "Hello!" and clicks Send
|
v
ws-send serializes the form: message=Hello!
|
v
Data sent through WebSocket to server
|
v
Server broadcasts to all connected clients:
<div hx-swap-oob="beforeend:#chat-messages">
<p><strong>Alice:</strong> Hello!</p>
</div>
|
v
HTMX receives the HTML, processes OOB swap
|
v
New message appears in #chat-messages for all users
Server-Side WebSocket Handler
Python (using websockets library)
import asyncio
import websockets
import json
connected_clients = set()
async def chat_handler(websocket, path):
connected_clients.add(websocket)
try:
async for raw_message in websocket:
data = json.loads(raw_message)
message_text = data.get('message', '').strip()
if message_text:
# Build the HTML fragment
html = f'''
<div hx-swap-oob="beforeend:#chat-messages">
<p><strong>User:</strong> {message_text}</p>
</div>
'''
# Broadcast to all connected clients
websockets.broadcast(connected_clients, html)
finally:
connected_clients.discard(websocket)
asyncio.run(websockets.serve(chat_handler, "localhost", 8765))
Node.js (using ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8765 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (raw) => {
const data = JSON.parse(raw);
const msg = data.message || '';
const html = `
<div hx-swap-oob="beforeend:#chat-messages">
<p><strong>User:</strong> ${msg}</p>
</div>`;
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(html);
}
});
});
ws.on('close', () => clients.delete(ws));
});
Full Chat UI Example
<div hx-ext="ws" ws-connect="wss://example.com/ws/chat">
<div id="chat-messages" style="height:400px; overflow-y:auto; border:1px solid #ccc; padding:10px">
<p>Connected to chat. Say hello!</p>
</div>
<form ws-send style="display:flex; gap:8px; margin-top:10px">
<input
type="text"
name="message"
placeholder="Type a message..."
autocomplete="off"
style="flex:1">
<button type="submit">Send</button>
</form>
</div>
Visual: ┌──────────────────────────────────────────────┐ │ Connected to chat. Say hello! │ │ Alice: Hello everyone! │ │ Bob: Hey Alice! │ │ Alice: How's the project going? │ └──────────────────────────────────────────────┘ [Type a message... ] [Send]
Handling WebSocket Events in JavaScript
The HTMX WebSocket extension fires events you can listen to for connection status management:
<script>
document.body.addEventListener('htmx:wsOpen', function() {
document.getElementById('status').textContent = 'Connected';
});
document.body.addEventListener('htmx:wsClose', function() {
document.getElementById('status').textContent = 'Disconnected — attempting reconnect...';
});
document.body.addEventListener('htmx:wsError', function(event) {
console.error('WebSocket error:', event.detail);
});
</script>
<span id="status">Connecting...</span>
Reconnection Strategy
Unlike SSE, WebSockets do not reconnect automatically. The HTMX WebSocket extension handles reconnection, but you should display a status indicator and potentially implement exponential backoff for unstable connections:
<script>
let reconnectDelay = 1000;
document.body.addEventListener('htmx:wsClose', function() {
setTimeout(() => {
// htmx.process re-initializes the WS element, triggering reconnect
htmx.process(document.getElementById('chat-container'));
reconnectDelay = Math.min(reconnectDelay * 2, 30000); // Max 30s
}, reconnectDelay);
});
</script>
Security: Always Use WSS in Production
Use ws:// only in local development. In production, always use wss:// (WebSocket Secure) — the WebSocket equivalent of HTTPS. Data sent over ws:// travels in plain text and can be intercepted.
Key Takeaway
HTMX's WebSocket extension enables two-way real-time communication using HTML attributes. Connect with ws-connect and send form data with ws-send. The server receives the message, builds an HTML fragment, and broadcasts it back. HTMX processes the incoming HTML and updates the DOM. Use WebSockets when you need the client to send data to the server in real time — chat, collaborative tools, and live games. Use SSE when you only need the server to push updates to the client.
