N

Real-time "is typing" (SSE companion)

A long-lived Server-Sent Events stream delivers presence/typing/push events. HTMX swaps handle everything else.

#When to use

Push-shaped data: presence dots, chat typing indicators, live dashboards, collaborative cursors. Don't reach for WebSocket unless you need bidirectional binary or sub-100ms latency — SSE is simpler and plays nicely with HTTP middleware.

#Stream endpoint

csharp
public async Task OnGet(CancellationToken ct)
{
    Response.Headers.Append("Content-Type", "text/event-stream");
    Response.Headers.Append("Cache-Control", "no-cache");

    await foreach (var evt in _presence.StreamAsync(ct))
    {
        await Response.WriteAsync($"event: {evt.Kind}\ndata: {evt.Json}\n\n", ct);
        await Response.Body.FlushAsync(ct);
    }
}

#Client (option A — htmx SSE extension)

Add the htmx SSE extension once in your layout:

html
<script src="https://unpkg.com/htmx-ext-sse@2.2.2/sse.js"></script>

Then declare the connection + the swap targets in markup:

html
<div hx-ext="sse" sse-connect="/chat/@Model.RoomId/events" sse-swap="typing">
    <span id="typing_indicator" aria-live="polite"></span>
</div>

The server emits event: typing followed by an HTML data payload, and htmx swaps it into the [sse-swap="typing"] element automatically.

#Client (option B — vanilla EventSource for non-HTML payloads)

html
<span id="typing_indicator" aria-live="polite"></span>

<script>
    const sse = new EventSource('/chat/@Model.RoomId/events');
    sse.addEventListener('typing', (e) => {
        const { user } = JSON.parse(e.data);
        document.getElementById('typing_indicator').textContent = `${user} is typing…`;
    });
    sse.addEventListener('typing-stop', () => {
        document.getElementById('typing_indicator').textContent = '';
    });
</script>

#Sending the "I'm typing" signal

Pure Alpine debounce on the input — no SSE on the way out, just a plain HTMX POST:

html
<input type="text" name="message"
       hx-post="/chat/@Model.RoomId/typing"
       hx-trigger="input changed delay:800ms"
       hx-swap="none" />

#Hybrid with HTMX

SSE handles ephemeral signals (typing, online). HTMX swaps handle durable actions (send message, mark read). That division keeps each channel doing what it's best at.

#Reconnection

Browsers automatically reconnect a closed EventSource after ~3 seconds. Include Last-Event-ID handling on the server if you need exactly-once delivery across reconnects.