Modern Chat Interface
A modern chat interface with real-time messaging, file uploads, emoji picker, and typing indicators
Responsive Design
Yes
Dark Mode Support
No
lines
325
Browser Compatibility
No
Live Preview
Interact with the component without leaving the page.
Modern Chat Interface
A comprehensive chat interface with real-time messaging capabilities, file uploads, emoji picker, typing indicators, and message status tracking.
Features
- Real-time Messaging: Instant message delivery and display
- File Upload: Support for images, documents, and other files
- Emoji Picker: Built-in emoji selection with categories
- Typing Indicators: Show when users are typing
- Message Status: Read receipts and delivery status
- User Presence: Online/offline status indicators
- Message Search: Search through chat history
- Responsive Design: Works on all device sizes
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Chat Interface</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<div class="chat-info">
<div class="avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=40&h=40&fit=crop&crop=face" alt="User">
<span class="status-indicator online"></span>
</div>
<div class="user-details">
<h3>Sarah Johnson</h3>
<span class="status-text">Online</span>
</div>
</div>
<div class="chat-actions">
<button class="action-btn" id="searchBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
<button class="action-btn" id="callBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
</svg>
</button>
<button class="action-btn" id="moreBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="12" cy="5" r="1"></circle>
<circle cx="12" cy="19" r="1"></circle>
</svg>
</button>
</div>
</div>
<div class="search-bar" id="searchBar">
<input type="text" placeholder="Search messages..." id="searchInput">
<button class="close-search" id="closeSearch">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="messages-container" id="messagesContainer">
<div class="message received">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="message-content">
<div class="message-bubble">
<p>Hey! How are you doing today?</p>
</div>
<div class="message-info">
<span class="message-time">10:30 AM</span>
</div>
</div>
</div>
<div class="message sent">
<div class="message-content">
<div class="message-bubble">
<p>I'm doing great! Just finished a new project. How about you?</p>
</div>
<div class="message-info">
<span class="message-time">10:32 AM</span>
<span class="message-status read">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>
</span>
</div>
</div>
</div>
<div class="message received">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="message-content">
<div class="message-bubble image-message">
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=300&h=200&fit=crop" alt="Shared image">
<p>Check out this amazing sunset I captured!</p>
</div>
<div class="message-info">
<span class="message-time">10:35 AM</span>
</div>
</div>
</div>
<div class="typing-indicator" id="typingIndicator">
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="Sarah">
</div>
<div class="typing-bubble">
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="message-input-container">
<div class="input-actions">
<button class="input-btn" id="attachBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.64 16.2a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
</button>
<button class="input-btn" id="emojiBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="10"></circle>
<path d="8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</button>
</div>
<div class="input-wrapper">
<textarea
id="messageInput"
placeholder="Type a message..."
rows="1"
></textarea>
<button class="send-btn" id="sendBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9"></polygon>
</svg>
</button>
</div>
</div>
<div class="modal" id="fileModal">
<div class="modal-content">
<div class="modal-header">
<h3>Share File</h3>
<button class="close-modal" id="closeFileModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="file-upload-area" id="fileUploadArea">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10,9 9,9 8,9"></polyline>
</svg>
<p>Drag and drop files here or click to browse</p>
<input type="file" id="fileInput" multiple accept="image.chat-header {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-info {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
position: relative;
}
.avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.status-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid white;
}
.status-indicator.online {
background: #4ade80;
}
.status-indicator.offline {
background: #94a3b8;
}
.user-details h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.status-text {
font-size: 12px;
opacity: 0.8;
}
.chat-actions {
display: flex;
gap: 8px;
}
.action-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
}.search-bar {
padding: 12px 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
display: none;
align-items: center;
gap: 12px;
}
.search-bar.active {
display: flex;
}
.search-bar input {
flex: 1;
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
outline: none;
}
.search-bar input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.close-search {
background: none;
border: none;
color: #64748b;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.close-search:hover {
background: #e2e8f0;
}.messages-container {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
background: #f8fafc;
}
.message {
display: flex;
gap: 8px;
max-width: 70%;
animation: messageSlide 0.3s ease;
}
.message.sent {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-avatar img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.message-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.message.sent .message-content {
align-items: flex-end;
}
.message-bubble {
padding: 12px 16px;
border-radius: 18px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
}
.message.sent .message-bubble {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message-bubble p {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
.image-message img {
width: 100%;
max-width: 300px;
border-radius: 12px;
margin-bottom: 8px;
}
.message-info {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #64748b;
}
.message.sent .message-info {
flex-direction: row-reverse;
}
.message-status {
display: flex;
align-items: center;
}
.message-status.read {
color: #667eea;
}.typing-indicator {
display: flex;
gap: 8px;
align-items: flex-end;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
}
.typing-indicator.active {
opacity: 1;
transform: translateY(0);
}
.typing-bubble {
background: white;
padding: 12px 16px;
border-radius: 18px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.typing-dots {
display: flex;
gap: 4px;
}
.typing-dots span {
width: 6px;
height: 6px;
background: #94a3b8;
border-radius: 50%;
animation: typingDot 1.4s infinite;
}
.typing-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dots span:nth-child(3) {
animation-delay: 0.4s;
}.message-input-container {
padding: 20px;
background: white;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 12px;
align-items: flex-end;
}
.input-actions {
display: flex;
gap: 8px;
}
.input-btn {
background: #f1f5f9;
border: none;
color: #64748b;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.input-btn:hover {
background: #e2e8f0;
color: #475569;
}
.input-wrapper {
flex: 1;
display: flex;
gap: 8px;
align-items: flex-end;
}
#messageInput {
flex: 1;
padding: 12px 16px;
border: 1px solid #e2e8f0;
border-radius: 12px;
font-size: 14px;
font-family: inherit;
resize: none;
outline: none;
min-height: 40px;
max-height: 120px;
line-height: 1.4;
}
#messageInput:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.send-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.send-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 16px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
animation: modalSlide 0.3s ease;
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h3 {
font-size: 18px;
font-weight: 600;
}
.close-modal {
background: none;
border: none;
color: #64748b;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.close-modal:hover {
background: #f1f5f9;
}
.file-upload-area {
padding: 40px 20px;
text-align: center;
border: 2px dashed #e2e8f0;
margin: 20px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.file-upload-area:hover {
border-color: #667eea;
background: #f8fafc;
}
.file-upload-area svg {
color: #94a3b8;
margin-bottom: 12px;
}
.file-upload-area p {
color: #64748b;
font-size: 14px;
}
#fileInput {
display: none;
}
.file-preview {
padding: 20px;
border-top: 1px solid #e2e8f0;
display: none;
}
.file-preview.active {
display: block;
}
.file-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
margin-bottom: 8px;
}
.file-item img {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 4px;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 500;
font-size: 14px;
margin-bottom: 2px;
}
.file-size {
font-size: 12px;
color: #64748b;
}.emoji-picker {
position: absolute;
bottom: 80px;
left: 20px;
width: 300px;
height: 350px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: none;
flex-direction: column;
z-index: 100;
}
.emoji-picker.active {
display: flex;
}
.emoji-categories {
display: flex;
padding: 12px;
border-bottom: 1px solid #e2e8f0;
gap: 4px;
}
.emoji-category {
background: none;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: all 0.2s ease;
}
.emoji-category:hover,
.emoji-category.active {
background: #f1f5f9;
}
.emoji-grid {
flex: 1;
padding: 12px;
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 4px;
overflow-y: auto;
}
.emoji-item {
background: none;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
font-size: 18px;
transition: all 0.2s ease;
}
.emoji-item:hover {
background: #f1f5f9;
transform: scale(1.2);
}@keyframes messageSlide {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes typingDot {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
@keyframes modalSlide {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}@media (max-width: 768px) {
body {
padding: 0;
}
.chat-container {
height: 100vh;
border-radius: 0;
max-width: none;
}
.message {
max-width: 85%;
}
.emoji-picker {
left: 10px;
right: 10px;
width: auto;
}
.modal-content {
width: 95%;
margin: 20px;
}
}
@media (max-width: 480px) {
.chat-header {
padding: 16px;
}
.messages-container {
padding: 16px;
}
.message-input-container {
padding: 16px;
}
.user-details h3 {
font-size: 14px;
}
.status-text {
font-size: 11px;
}
}
JavaScript Functionality
class ChatInterface {
constructor() {
this.messages = [];
this.isTyping = false;
this.currentUser = 'user';
this.otherUser = 'Sarah Johnson';
this.initializeElements();
this.initializeEventListeners();
this.initializeEmojis();
this.autoResizeTextarea();
this.simulateTyping();
}
initializeElements() {
this.messagesContainer = document.getElementById('messagesContainer');
this.messageInput = document.getElementById('messageInput');
this.sendBtn = document.getElementById('sendBtn');
this.searchBtn = document.getElementById('searchBtn');
this.searchBar = document.getElementById('searchBar');
this.searchInput = document.getElementById('searchInput');
this.closeSearch = document.getElementById('closeSearch');
this.attachBtn = document.getElementById('attachBtn');
this.emojiBtn = document.getElementById('emojiBtn');
this.fileModal = document.getElementById('fileModal');
this.closeFileModal = document.getElementById('closeFileModal');
this.fileUploadArea = document.getElementById('fileUploadArea');
this.fileInput = document.getElementById('fileInput');
this.filePreview = document.getElementById('filePreview');
this.emojiPicker = document.getElementById('emojiPicker');
this.emojiGrid = document.getElementById('emojiGrid');
this.typingIndicator = document.getElementById('typingIndicator');
}
initializeEventListeners() {
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
this.searchBtn.addEventListener('click', () => this.toggleSearch());
this.closeSearch.addEventListener('click', () => this.toggleSearch());
this.searchInput.addEventListener('input', (e) => this.searchMessages(e.target.value));
this.attachBtn.addEventListener('click', () => this.openFileModal());
this.closeFileModal.addEventListener('click', () => this.closeFileModalHandler());
this.fileUploadArea.addEventListener('click', () => this.fileInput.click());
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
this.fileUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
this.fileUploadArea.style.borderColor = '#667eea';
});
this.fileUploadArea.addEventListener('dragleave', () => {
this.fileUploadArea.style.borderColor = '#e2e8f0';
});
this.fileUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
this.fileUploadArea.style.borderColor = '#e2e8f0';
this.handleFileSelect({ target: { files: e.dataTransfer.files } });
});
this.emojiBtn.addEventListener('click', () => this.toggleEmojiPicker());
document.addEventListener('click', (e) => {
if (!this.emojiPicker.contains(e.target) && !this.emojiBtn.contains(e.target)) {
this.emojiPicker.classList.remove('active');
}
});
this.messageInput.addEventListener('input', () => {
this.updateSendButton();
this.simulateUserTyping();
});
}
sendMessage() {
const text = this.messageInput.value.trim();
if (!text) return;
const message = {
id: Date.now(),
text: text,
sender: this.currentUser,
timestamp: new Date(),
status: 'sent'
};
this.addMessage(message);
this.messageInput.value = '';
this.updateSendButton();
this.autoResizeTextarea();
setTimeout(() => {
this.simulateResponse();
}, 1000 + Math.random() * 2000);
}
addMessage(message) {
this.messages.push(message);
const messageElement = document.createElement('div');
messageElement.className = `message ${message.sender === this.currentUser ? 'sent' : 'received'}`;
if (message.sender !== this.currentUser) {
messageElement.innerHTML = `
<div class="message-avatar">
<img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face" alt="${this.otherUser}">
</div>
<div class="message-content">
<div class="message-bubble">
${message.image ? `<img src="${message.image}" alt="Shared image">` : ''}
<p>${this.escapeHtml(message.text)}</p>
</div>
<div class="message-info">
<span class="message-time">${this.formatTime(message.timestamp)}</span>
</div>
</div>
`;
} else {
messageElement.innerHTML = `
<div class="message-content">
<div class="message-bubble">
${message.image ? `<img src="${message.image}" alt="Shared image">` : ''}
<p>${this.escapeHtml(message.text)}</p>
</div>
<div class="message-info">
<span class="message-time">${this.formatTime(message.timestamp)}</span>
<span class="message-status ${message.status}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>
</span>
</div>
</div>
`;
}
this.messagesContainer.insertBefore(messageElement, this.typingIndicator);
this.scrollToBottom();
if (message.sender === this.currentUser) {
setTimeout(() => {
const statusElement = messageElement.querySelector('.message-status');
if (statusElement) {
statusElement.classList.add('read');
}
}, 1000);
}
}
simulateResponse() {
const responses = [
"That's interesting! Tell me more.",
"I completely agree with you.",
"Thanks for sharing that!",
"That sounds amazing!",
"I'll definitely check that out.",
"Great idea! Let's do it.",
"That made me smile 😊",
"I'm so excited about this!"
];
const response = {
id: Date.now(),
text: responses[Math.floor(Math.random() * responses.length)],
sender: this.otherUser,
timestamp: new Date()
};
this.addMessage(response);
}
simulateTyping() {
setInterval(() => {
if (Math.random() < 0.1 && !this.isTyping) {
this.showTypingIndicator();
setTimeout(() => {
this.hideTypingIndicator();
if (Math.random() < 0.7) {
this.simulateResponse();
}
}, 2000 + Math.random() * 3000);
}
}, 5000);
}
simulateUserTyping() {
console.log('User is typing...');
}
showTypingIndicator() {
this.isTyping = true;
this.typingIndicator.classList.add('active');
this.scrollToBottom();
}
hideTypingIndicator() {
this.isTyping = false;
this.typingIndicator.classList.remove('active');
}
toggleSearch() {
this.searchBar.classList.toggle('active');
if (this.searchBar.classList.contains('active')) {
this.searchInput.focus();
} else {
this.searchInput.value = '';
this.clearSearchHighlights();
}
}
searchMessages(query) {
this.clearSearchHighlights();
if (!query.trim()) return;
const messages = this.messagesContainer.querySelectorAll('.message');
messages.forEach(message => {
const text = message.textContent.toLowerCase();
if (text.includes(query.toLowerCase())) {
message.style.backgroundColor = '#fef3c7';
message.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
clearSearchHighlights() {
const messages = this.messagesContainer.querySelectorAll('.message');
messages.forEach(message => {
message.style.backgroundColor = '';
});
}
openFileModal() {
this.fileModal.classList.add('active');
}
closeFileModalHandler() {
this.fileModal.classList.remove('active');
this.filePreview.classList.remove('active');
this.filePreview.innerHTML = '';
this.fileInput.value = '';
}
handleFileSelect(event) {
const files = Array.from(event.target.files);
if (files.length === 0) return;
this.filePreview.innerHTML = '';
this.filePreview.classList.add('active');
files.forEach(file => {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
fileItem.innerHTML = `
<img src="${e.target.result}" alt="${file.name}">
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${this.formatFileSize(file.size)}</div>
</div>
<button class="send-file-btn" onclick="chatInterface.sendFile('${e.target.result}', '${file.name}')">
Send
</button>
`;
};
reader.readAsDataURL(file);
} else {
fileItem.innerHTML = `
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
</svg>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${this.formatFileSize(file.size)}</div>
</div>
<button class="send-file-btn" onclick="chatInterface.sendFile(null, '${file.name}')">
Send
</button>
`;
}
this.filePreview.appendChild(fileItem);
});
}
sendFile(imageData, fileName) {
const message = {
id: Date.now(),
text: imageData ? `Shared: ${fileName}` : `Shared file: ${fileName}`,
sender: this.currentUser,
timestamp: new Date(),
status: 'sent',
image: imageData
};
this.addMessage(message);
this.closeFileModalHandler();
setTimeout(() => {
const response = {
id: Date.now(),
text: imageData ? "Nice photo! 📸" : "Thanks for sharing the file!",
sender: this.otherUser,
timestamp: new Date()
};
this.addMessage(response);
}, 1500);
}
toggleEmojiPicker() {
this.emojiPicker.classList.toggle('active');
}
initializeEmojis() {
const emojiCategories = {
smileys: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳'],
people: ['👶', '🧒', '👦', '👧', '🧑', '👱', '👨', '🧔', '👩', '🧓', '👴', '👵', '🙍', '🙎', '🙅', '🙆', '💁', '🙋', '🧏', '🙇', '🤦', '🤷', '👮', '🕵️', '💂', '👷', '🤴', '👸', '👳', '👲'],
nature: ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇'],
food: ['🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌶️', '🌽', '🥕', '🧄', '🧅', '🥔', '🍠', '🥐', '🍞'],
activities: ['⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅', '⛳', '🪁', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛷', '⛸️'],
travel: ['🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🏍️', '🛵', '🚲', '🛴', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞'],
objects: ['💡', '🔦', '🕯️', '🪔', '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '💰', '💳', '💎', '⚖️', '🧰', '🔧', '🔨', '⚒️', '🛠️', '⛏️', '🔩', '⚙️', '🧱', '⛓️', '🧲', '🔫', '💣', '🧨', '🪓'],
symbols: ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐']
};
const categoryButtons = document.querySelectorAll('.emoji-category');
categoryButtons.forEach(button => {
button.addEventListener('click', () => {
categoryButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
this.loadEmojiCategory(button.dataset.category, emojiCategories);
});
});
this.loadEmojiCategory('smileys', emojiCategories);
}
loadEmojiCategory(category, emojiCategories) {
this.emojiGrid.innerHTML = '';
emojiCategories[category].forEach(emoji => {
const emojiButton = document.createElement('button');
emojiButton.className = 'emoji-item';
emojiButton.textContent = emoji;
emojiButton.addEventListener('click', () => {
this.insertEmoji(emoji);
});
this.emojiGrid.appendChild(emojiButton);
});
}
insertEmoji(emoji) {
const cursorPos = this.messageInput.selectionStart;
const textBefore = this.messageInput.value.substring(0, cursorPos);
const textAfter = this.messageInput.value.substring(cursorPos);
this.messageInput.value = textBefore + emoji + textAfter;
this.messageInput.focus();
this.messageInput.setSelectionRange(cursorPos + emoji.length, cursorPos + emoji.length);
this.updateSendButton();
this.emojiPicker.classList.remove('active');
}
autoResizeTextarea() {
this.messageInput.style.height = 'auto';
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
}
updateSendButton() {
const hasText = this.messageInput.value.trim().length > 0;
this.sendBtn.disabled = !hasText;
}
scrollToBottom() {
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
}
formatTime(date) {
return date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
const chatInterface = new ChatInterface();
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
const welcomeMessage = {
id: Date.now(),
text: "Welcome to our chat! Feel free to send messages, share files, or use emojis. 🎉",
sender: 'Sarah Johnson',
timestamp: new Date()
};
chatInterface.addMessage(welcomeMessage);
}, 1000);
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
chatInterface.toggleSearch();
}
if (e.key === 'Escape') {
chatInterface.emojiPicker.classList.remove('active');
chatInterface.fileModal.classList.remove('active');
if (chatInterface.searchBar.classList.contains('active')) {
chatInterface.toggleSearch();
}
}
});
});
Usage Examples
Basic Implementation
const chat = new ChatInterface();
chat.addMessage({
id: Date.now(),
text: "Hello, world!",
sender: "user",
timestamp: new Date(),
status: "sent"
});
Custom Configuration
const chat = new ChatInterface({
currentUser: 'john_doe',
otherUser: 'Jane Smith',
autoScroll: true,
enableTypingIndicator: true,
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedFileTypes: ['image/*', 'application/pdf']
});
Integration with Real-time Services
const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
chat.addMessage(message);
};
chat.sendMessage = function() {
const text = this.messageInput.value.trim();
if (!text) return;
const message = {
id: Date.now(),
text: text,
sender: this.currentUser,
timestamp: new Date()
};
socket.send(JSON.stringify(message));
this.addMessage(message);
this.messageInput.value = '';
};
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Features Included
✅ Real-time messaging interface
✅ File upload with drag & drop
✅ Emoji picker with categories
✅ Typing indicators
✅ Message status (sent/read)
✅ User presence indicators
✅ Message search functionality
✅ Responsive design
✅ Keyboard shortcuts
✅ Auto-resizing input
✅ Smooth animations
✅ Accessibility support
Customization Options
- Colors: Modify CSS custom properties for theming
- Layout: Adjust container dimensions and positioning
- Features: Enable/disable specific functionality
- Integrations: Connect to real-time services
- File Types: Configure allowed upload formats
- Emoji Sets: Customize available emoji categories
This chat interface provides a solid foundation for building modern messaging applications with all the essential features users expect.
HTML
50
lines
CSS
222
lines
JavaScript
53
lines
<div class="chat-container">
<div class="chat-header">
<div class="user-info">
<div class="avatar">AI</div>
<div class="user-details">
<h3>AI Assistant</h3>
<span class="status online">Online</span>
</div>
</div>
<div class="chat-actions">
<button class="action-btn">📞</button>
<button class="action-btn">📹</button>
<button class="action-btn">⚙️</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message received">
<div class="message-avatar">AI</div>
<div class="message-content">
<p>Hello! How can I help you today?</p>
<span class="message-time">10:30 AM</span>
</div>
</div>
<div class="message sent">
<div class="message-content">
<p>I need help with my project</p>
<span class="message-time">10:31 AM</span>
</div>
</div>
<div class="message received">
<div class="message-avatar">AI</div>
<div class="message-content">
<p>I'd be happy to help! What kind of project are you working on?</p>
<span class="message-time">10:31 AM</span>
</div>
</div>
</div>
<div class="chat-input-container">
<div class="chat-input">
<input type="text" id="messageInput" placeholder="Type your message..." />
<button class="emoji-btn">😊</button>
<button class="attach-btn">📎</button>
<button class="send-btn" id="sendBtn">➤</button>
</div>
</div>
</div>