Category · Communication Difficulty Level · Advanced Published on · January 20, 2025

Modern Chat Interface

A modern chat interface with real-time messaging, file uploads, emoji picker, and typing indicators

#chat #messaging #real-time #interface #communication

Responsive Design

Yes

Dark Mode Support

No

lines

327

Browser Compatibility

No

Live Preview

Interact with the component without leaving the page.

600px

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>

              
50lines
1523characters
HTMLLanguage

Related Code Snippets

Explore template packs

Need larger building blocks? Browse responsive landing pages and component bundles.

Open HTML Template Library →