Live Chat
Real-time messaging with typing indicators, quick replies, and LiveView-powered state.
AS
Ava Stone
Senior Product Specialist · Online now
Live
The demo keeps conversation state inside the LiveView, streams responses, and simulates an agent reply so you can feel the real interaction cadence.
Highlights
- Modern glass shell with gradient accents and subtle motion.
- Live typing indicator plus quick reply macros driven by LiveView events.
- Fully stateful example—messages stream, replies queue, and typing clears automatically.
<.live_chat
id="support-chat"
messages={@chat_messages}
current_user={@chat_current_user}
typing_users={@chat_typing_users}
>
<:header>...</:header>
<:toolbar>...</:toolbar>
</.live_chat>
LiveView Wiring
The docs LiveView keeps per-chat state using dynamic assigns. Here’s the distilled version you can copy into your project:
defmodule MyAppWeb.SupportLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
messages = demo_messages()
{:ok,
socket
|> assign(:chat_current_user, "user-1")
|> assign(:chat_typing_users, [])
|> assign(:chat_reply_index, 0)
|> assign(:"support-chat_messages", messages)}
end
def handle_event("chat-send", %{"id" => "support-chat", "message" => message}, socket) do
trimmed = String.trim(message || "")
if trimmed == "" do
{:noreply, socket}
else
socket =
socket
|> append_message("support-chat", %{text: trimmed, user_id: "user-1", user_name: "You"})
|> assign(:chat_typing_users, [])
Process.send_after(self(), {:show_typing, "support-chat"}, 400)
Process.send_after(self(), {:bot_reply, "support-chat"}, 1400)
{:noreply, socket}
end
end
def handle_event("chat-typing", %{"id" => "support-chat"}, socket) do
Process.send_after(self(), {:clear_typing, "support-chat"}, 1200)
{:noreply, assign(socket, :chat_typing_users, ["Support Bot"])}
end
def handle_event("chat-quick-reply", %{"id" => "support-chat", "text" => text}, socket) do
handle_event("chat-send", %{"id" => "support-chat", "message" => text}, socket)
end
def handle_info({:show_typing, "support-chat"}, socket) do
{:noreply, assign(socket, :chat_typing_users, ["Support Bot"])}
end
def handle_info({:clear_typing, "support-chat"}, socket) do
{:noreply, assign(socket, :chat_typing_users, [])}
end
def handle_info({:bot_reply, "support-chat"}, socket) do
replies = demo_replies()
idx = rem(socket.assigns[:chat_reply_index], length(replies))
socket =
socket
|> assign(:chat_reply_index, socket.assigns[:chat_reply_index] + 1)
|> assign(:chat_typing_users, [])
|> append_message("support-chat", %{
text: Enum.at(replies, idx),
user_id: "bot",
user_name: "Support Bot"
})
{:noreply, socket}
end
defp append_message(socket, id, attrs) do
messages = Map.get(socket.assigns, :"#{id}_messages", [])
assign(socket, :"#{id}_messages", messages ++ [Map.put_new(attrs, :timestamp, DateTime.utc_now())])
end
end