Real-time updates with Server-Sent Events and polling.
Server-Sent Events (SSE)
Use data-sse to connect to an SSE endpoint:
<ul id="notifications" data-sse="/events" data-swap="prepend"> <!-- New notifications appear here --></ul>
When messages arrive, they're swapped into the element using the specified strategy.
SSE Server Example
Your server should send events in this format:
" {="" res.setHeader('Content-Type',="" 'text="" event-stream');="" res.setHeader('Cache-Control',="" 'no-cache');="" res.setHeader('Connection',="" 'keep-alive');="" Send="" a="" message="" res.write('data:="" <li>New="" notification!<="" li>\n\n');="" periodically="" setInterval(()=">" <li>Update="" at="" '="" +="" Date.now()="" '<="" },="" 5000);="" });<="" code-block=""><h2>Named Events</h2><p>By default, an element with <code>data-sse</code> listens only for the standard SSE <code>message</code> event (i.e. lines sent without an <code>event:</code> field). To subscribe to specific named events, add <code>data-sse-events</code> with a comma-separated list:</p><code-block language="html"><div data-sse="/events" data-sse-events="user-joined,message,typing" data-target="self" data-swap="append"></div>
When data-sse-events is set, html★ calls source.addEventListener() for each named type, so the server can dispatch different kinds of updates over a single connection:
event: user-joineddata: <li class="join">Alice joined the room</li> event: messagedata: <li class="msg">Hello everyone!</li> event: typingdata: <li class="typing">Bob is typing…</li>
Without data-sse-events, only unnamed messages (plain data: lines with no event: field) are handled:
data: <li>This is a default message event</li>
Tip: You can combine both — the default
onmessagehandler always fires for unnamed messages, and each entry indata-sse-eventsadds a named listener on top of that.
State Attributes
html★ automatically sets data attributes on elements to reflect connection state. Use these for CSS-driven status indicators:
| Attribute | When Set |
|---|---|
[data-sse-connected] | SSE connection opened successfully |
[data-sse-error] | SSE connection error |
[data-poll-error] | Polling request failed |
When an SSE connection opens, data-sse-connected is added and data-sse-error is removed. On error, data-sse-error is set. For polling, data-poll-error is set whenever a fetch fails (non-OK response or network error).
/* Connection status indicator */[data-sse-connected]::before { content: "● "; color: green;} [data-sse-error]::before { content: "● "; color: red;} /* Dim polling content on error */[data-poll-error] { opacity: 0.5;} /* Show error message on polling failure */[data-poll-error]::after { content: "Failed to load — retrying…"; display: block; color: var(--color-danger, red); font-size: 0.875rem;}
Polling
For servers that don't support SSE, use polling. Add data-poll with an interval and data-href with the URL to fetch:
<!-- Poll every 5 seconds --><div data-href="/api/status" data-poll="5s" data-target="self"> Loading status...</div> <!-- Poll every 500 milliseconds --><div data-href="/api/typing" data-poll="500ms" data-target="self"></div> <!-- Poll every 2 minutes --><div data-href="/api/summary" data-poll="2m" data-target="self"></div>
Supported interval formats:
| Format | Unit | Example |
|---|---|---|
500ms | milliseconds | data-poll="500ms" |
5s | seconds | data-poll="5s" |
1m | minutes | data-poll="1m" |
2000 | milliseconds (plain number) | data-poll="2000" |
A plain number without a unit suffix is treated as milliseconds. The default swap strategy for polling is inner (replacing the element's content with the response).
Closing Connections
SSE connections and polling intervals are automatically cleaned up when their elements are removed from the DOM (via a MutationObserver). You can also stop them programmatically:
<p><code>closeSSE(el)</code> closes the underlying <code>EventSource</code> and removes the <code>data-sse-connected</code> attribute. <code>closeAllSSE()</code> does the same for every active connection. <code>stopPolling(el)</code> clears the element's polling interval so no further requests are made.</p><h2>Live Dashboard Example</h2><code-block language="html"><div class="dashboard"> <!-- Live stats via polling --> <div class="stats" data-href="/api/stats" data-poll="30s" data-target="self"> Loading stats... </div> <!-- Live activity feed via SSE --> <div class="activity"> <h3>Live Activity</h3> <ul data-sse="/activity/stream" data-swap="prepend"> <!-- Events appear here --> </ul> </div></div>
SSE vs Polling: SSE is more efficient for real-time updates — the connection stays open. Polling is simpler and works with any server, but uses more bandwidth.
Warning: html★ intentionally doesn't support WebSockets. WebSocket requires a different programming model. If you need WebSocket, use a dedicated library alongside html★.