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
ReactTailwind
TSX
npx @uiblox/cli add chat-interface

Preview

Layout:

Messages

SA

Sarah Anderson

2m

That sounds great!

2
DT

Design Team

1h

Mockups ready for review

AC

Alex Chen

3h

Thanks 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"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>; }