Blox/
Chat Interface
A complete chat application interface with conversation list, message bubbles, online status, and message input. Perfect for messaging apps and customer support systems.
ConversationsMessage BubblesOnline StatusRead Receipts
Installation
Terminal
TSXReactTailwind
npx @uiblox/cli add chat-interfacePreview
Layout:
Messages
SA
Sarah Anderson
2mThat sounds great!
DT
Design Team
1hMockups ready for review
AC
Alex Chen
3hThanks for the update!
SA
Sarah Anderson
Online
Sarah
Hey! How's the project?
10:30 AM
Going well! Just finished the dashboard.
10:32 AM
Sarah
That's awesome! Can you share screenshots?
10:33 AM
Sure, sending them now!
10:35 AM
Use Cases
Messaging Apps
Build real-time chat applications with rich messaging features.
Customer Support
Create live chat widgets for customer service and helpdesk systems.
Team Collaboration
Build internal communication tools for teams and organizations.
Social Platforms
Add direct messaging features to social networking applications.
Source Code
chat-interface.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215"use client"; import { useState, useRef, useEffect } from "react"; import { cn } from "@/lib/utils"; interface Message { id: string; content: string; sender: "user" | "other"; timestamp: string; avatar?: string; name?: string; status?: "sent" | "delivered" | "read"; } interface Conversation { id: string; name: string; avatar: string; lastMessage: string; time: string; unread: number; online: boolean; } const conversations: Conversation[] = [ { id: "1", name: "Sarah Anderson", avatar: "SA", lastMessage: "That sounds great! Let me know...", time: "2m", unread: 2, online: true }, { id: "2", name: "Design Team", avatar: "DT", lastMessage: "New mockups are ready for review", time: "1h", unread: 0, online: false }, { id: "3", name: "Alex Chen", avatar: "AC", lastMessage: "Thanks for the update!", time: "3h", unread: 0, online: true }, { id: "4", name: "Marketing", avatar: "MK", lastMessage: "Campaign results are in", time: "1d", unread: 5, online: false }, ]; const initialMessages: Message[] = [ { id: "1", content: "Hey! How's the project coming along?", sender: "other", timestamp: "10:30 AM", name: "Sarah" }, { id: "2", content: "Going well! Just finished the main dashboard components.", sender: "user", timestamp: "10:32 AM", status: "read" }, { id: "3", content: "That's awesome! Can you share some screenshots?", sender: "other", timestamp: "10:33 AM", name: "Sarah" }, { id: "4", content: "Sure, I'll send them over in a bit. Working on the final touches.", sender: "user", timestamp: "10:35 AM", status: "read" }, { id: "5", content: "That sounds great! Let me know if you need any feedback.", sender: "other", timestamp: "10:36 AM", name: "Sarah" }, ]; export function ChatInterface() { const [messages, setMessages] = useState(initialMessages); const [newMessage, setNewMessage] = useState(""); const [activeConversation, setActiveConversation] = useState("1"); const messagesEndRef = useRef<HTMLDivElement>(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { scrollToBottom(); }, [messages]); const sendMessage = () => { if (!newMessage.trim()) return; const message: Message = { id: Date.now().toString(), content: newMessage, sender: "user", timestamp: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), status: "sent", }; setMessages([...messages, message]); setNewMessage(""); }; return ( <div className="flex h-[600px] bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-slate-800 overflow-hidden"> {/* Sidebar */} <div className="w-80 border-r border-slate-200 dark:border-slate-800 flex flex-col"> {/* Header */} <div className="p-4 border-b border-slate-200 dark:border-slate-800"> <div className="flex items-center justify-between mb-4"> <h2 className="text-lg font-semibold text-slate-900 dark:text-white">Messages</h2> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"> <EditIcon /> </button> </div> <div className="relative"> <SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" /> <input type="text" placeholder="Search conversations..." className="w-full pl-10 pr-4 py-2.5 bg-slate-100 dark:bg-slate-800 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-purple-500" /> </div> </div> {/* Conversation List */} <div className="flex-1 overflow-y-auto"> {conversations.map((conv) => ( <button key={conv.id} onClick={() => setActiveConversation(conv.id)} className={cn( "w-full flex items-center gap-3 p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors", activeConversation === conv.id && "bg-purple-50 dark:bg-purple-900/20" )} > <div className="relative"> <div className="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold"> {conv.avatar} </div> {conv.online && ( <span className="absolute bottom-0 right-0 w-3.5 h-3.5 bg-emerald-500 border-2 border-white dark:border-slate-900 rounded-full" /> )} </div> <div className="flex-1 min-w-0 text-left"> <div className="flex items-center justify-between"> <p className="font-medium text-slate-900 dark:text-white truncate">{conv.name}</p> <span className="text-xs text-slate-500">{conv.time}</span> </div> <p className="text-sm text-slate-500 truncate">{conv.lastMessage}</p> </div> {conv.unread > 0 && ( <span className="w-5 h-5 bg-purple-600 text-white text-xs font-bold rounded-full flex items-center justify-center"> {conv.unread} </span> )} </button> ))} </div> </div> {/* Chat Area */} <div className="flex-1 flex flex-col"> {/* Chat Header */} <div className="flex items-center justify-between px-6 py-4 border-b border-slate-200 dark:border-slate-800"> <div className="flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold"> SA </div> <div> <p className="font-semibold text-slate-900 dark:text-white">Sarah Anderson</p> <p className="text-xs text-emerald-600">Online</p> </div> </div> <div className="flex items-center gap-2"> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><PhoneIcon /></button> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><VideoIcon /></button> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><MoreIcon /></button> </div> </div> {/* Messages */} <div className="flex-1 overflow-y-auto p-6 space-y-4"> {messages.map((message) => ( <div key={message.id} className={cn("flex", message.sender === "user" ? "justify-end" : "justify-start")} > <div className={cn("max-w-[70%]", message.sender === "user" ? "order-2" : "")}> {message.sender === "other" && ( <p className="text-xs text-slate-500 mb-1 ml-1">{message.name}</p> )} <div className={cn( "px-4 py-3 rounded-2xl", message.sender === "user" ? "bg-purple-600 text-white rounded-br-md" : "bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white rounded-bl-md" )} > <p className="text-sm">{message.content}</p> </div> <div className={cn("flex items-center gap-1 mt-1", message.sender === "user" ? "justify-end" : "")}> <span className="text-xs text-slate-400">{message.timestamp}</span> {message.sender === "user" && message.status === "read" && ( <CheckCheckIcon className="text-purple-500" /> )} </div> </div> </div> ))} <div ref={messagesEndRef} /> </div> {/* Input */} <div className="p-4 border-t border-slate-200 dark:border-slate-800"> <div className="flex items-center gap-3"> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><PlusIcon /></button> <input type="text" value={newMessage} onChange={(e) => setNewMessage(e.target.value)} onKeyPress={(e) => e.key === "Enter" && sendMessage()} placeholder="Type a message..." className="flex-1 px-4 py-2.5 bg-slate-100 dark:bg-slate-800 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-purple-500" /> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><EmojiIcon /></button> <button onClick={sendMessage} className="p-2.5 bg-purple-600 hover:bg-purple-700 text-white rounded-xl" > <SendIcon /> </button> </div> </div> </div> </div> ); } function EditIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>; } function SearchIcon({ className }: { className?: string }) { return <svg className={cn("w-5 h-5", className)} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>; } function PhoneIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /></svg>; } function VideoIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>; } function MoreIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" /></svg>; } function PlusIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>; } function EmojiIcon() { return <svg className="w-5 h-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>; } function SendIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg>; } function CheckCheckIcon({ className }: { className?: string }) { return <svg className={cn("w-4 h-4", className)} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>; }