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
327
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">
<!-- Chat Header -->
<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>
<!-- Search Bar -->
<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>
<!-- Messages Container -->
<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>
<!-- Typing Indicator -->
<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>
<!-- Message Input -->
<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>
<!-- File Upload Modal -->
<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/*,application/pdf,.doc,.docx">
</div>
<div class="file-preview" id="filePreview"></div>
</div>
</div>
<!-- Emoji Picker -->
<div class="emoji-picker" id="emojiPicker">
<div class="emoji-categories">
<button class="emoji-category active" data-category="smileys">😀</button>
<button class="emoji-category" data-category="people">👤</button>
<button class="emoji-category" data-category="nature">🌿</button>
<button class="emoji-category" data-category="food">🍎</button>
<button class="emoji-category" data-category="activities">⚽</button>
<button class="emoji-category" data-category="travel">🚗</button>
<button class="emoji-category" data-category="objects">💡</button>
<button class="emoji-category" data-category="symbols">❤️</button>
</div>
<div class="emoji-grid" id="emojiGrid">
<!-- Emojis will be populated by JavaScript -->
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>CSS Styles
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.chat-container {
width: 100%;
max-width: 800px;
height: 600px;
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
/* Chat Header */
.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 */
.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 */
.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 */
.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 */
.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 */
.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 */
.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);
}
/* Animations */
@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);
}
}
/* Responsive Design */
@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();
// Simulate typing indicator
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() {
// Send message
this.sendBtn.addEventListener('click', () => this.sendMessage());
this.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Search functionality
this.searchBtn.addEventListener('click', () => this.toggleSearch());
this.closeSearch.addEventListener('click', () => this.toggleSearch());
this.searchInput.addEventListener('input', (e) => this.searchMessages(e.target.value));
// File upload
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));
// Drag and drop
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 } });
});
// Emoji picker
this.emojiBtn.addEventListener('click', () => this.toggleEmojiPicker());
// Close modals on outside click
document.addEventListener('click', (e) => {
if (!this.emojiPicker.contains(e.target) && !this.emojiBtn.contains(e.target)) {
this.emojiPicker.classList.remove('active');
}
});
// Typing simulation
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();
// Simulate response
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();
// Mark as read after a delay
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() {
// In a real app, this would send typing status to other users
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();
// Simulate response
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: ['❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐']
};
// Initialize emoji categories
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);
});
});
// Load default category
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;
}
}
// Initialize chat interface
const chatInterface = new ChatInterface();
// Add some demo functionality
document.addEventListener('DOMContentLoaded', () => {
// Simulate initial messages
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);
// Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K to focus search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
chatInterface.toggleSearch();
}
// Escape to close modals
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
// Initialize the chat interface
const chat = new ChatInterface();
// Send a message programmatically
chat.addMessage({
id: Date.now(),
text: "Hello, world!",
sender: "user",
timestamp: new Date(),
status: "sent"
});Custom Configuration
// Customize the chat interface
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
// WebSocket integration example
const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
chat.addMessage(message);
};
// Override send message to use WebSocket
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
55
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>