Building your own messaging app like Kik might sound hard, but this guide makes it simple. We will walk you through each step to create a chat app with all the key features users expect.
First, you will learn how to set up basic user registration using usernames instead of phone numbers. Then we show you how to add messaging functions, group chats, and private conversations.
The guide also covers video chat options and media sharing for photos and videos. We explain how to design a simple user interface that people find easy to use. By the end, you will understand all the main parts needed to build your own messaging app like Kik.
Must-Have Features of Messaging Apps
Modern messaging apps need essential features that make communication easy and enjoyable. These five features are what users expect from any good messaging platform today.
Message privacy protection: Good messaging apps keep your messages safe and private. This means only you and the person you are texting can see what you wrote. Other people cannot read your personal conversations. This protection keeps your private talks secure from strangers.
Group chat functions: Group messaging lets people talk with multiple friends, family members, or coworkers at once. Users can create groups, add or remove members, and share updates with everyone quickly. Good group features include naming groups and setting different permissions for members.
Media sharing options: People want to share photos, videos, documents, and voice messages easily. The best apps allow users to send large files without losing quality. Quick photo editing tools and the ability to share files from cloud storage make this feature even better.
Message read receipts: Read receipts show when someone has seen your message. This feature helps users know if their message was delivered and read. Some apps let people turn this feature on or off based on their privacy needs.
Works on all your devices: Users want their messages on all their devices. Good apps let people start chatting on their phone and continue on their computer. All message history, contacts, and settings should update on every device at the same time.
These features make messaging apps useful and reliable for daily communication.
Create Your Own Messaging App Like Kik with ZEGOCLOUD
Creating a messaging app like Kik is much easier when you use ZEGOCLOUD's ready-made SDKs. Kik became popular because it lets people chat without phone numbers and includes features like group chats and video calls. With ZEGOCLOUD, you get all the complex real-time communication technology built for you.
In this section, we'll build a complete messaging app with chat, video calls, and group messaging. The app will work across different devices and have a clean, modern interface.
Prerequisites
- A ZEGOCLOUD account - sign up at console.zegocloud.com.
- Your AppID and ServerSecret from ZEGOCLOUD Admin Console.
- Node.js installed (version 16 or higher).
- Basic knowledge of React and JavaScript.
- A modern web browser for testing.
If you're ready with these, let's start building!
1. Create Your React Project
First, create a new React project using Vite (the modern, fast build tool):
npm create vite@latest messaging-app -- --template react
cd messaging-app
npm install
This creates a modern React app with all the necessary development tools configured.
2. Install ZEGOCLOUD SDKs
Now install the required ZEGOCLOUD SDKs for messaging and video calling:
npm install zego-zim-web zego-express-engine-webrtc
Your project structure will look like this:
messaging-app/
├── public/
├── src/
│ ├── components/
│ ├── App.jsx
│ ├── App.css
│ ├── main.jsx
│ └── index.css
├── package.json
└── vite.config.js
3. Set Up Your App Configuration
Create a configuration file for your ZEGOCLOUD credentials. Create src/config.js
:
export const CONFIG = {
appID:your_app_id,
serverSecret: "your_server_secret",
server: "your_server_address"
};
export function generateToken(userID) {
// DEVELOPMENT ONLY: Pre-generated tokens for testing
// In production, generate tokens on your backend server for security
const devTokens = {
"samuel": "token_from_zegocloud_console",
"alice": "token_from_zegocloud_console"
};
return devTokens[userID] || "";
}
The configuration above handles your ZEGOCLOUD credentials and provides a simple token generation function for authentication.
4. Create Your App Components
Now let's build the main App component. Replace the contents of src/App.jsx
:
import React, { useState, useEffect, useRef } from 'react';
import { ZIM } from 'zego-zim-web';
import { ZegoExpressEngine } from 'zego-express-engine-webrtc';
import { CONFIG, generateToken } from './config';
import './App.css';
const LoginScreen = ({ onLogin }) => {
const [username, setUsername] = useState('');
const [loading, setLoading] = useState(false);
const handleLogin = async (e) => {
e.preventDefault();
if (!username.trim()) {
alert('Please enter a username');
return;
}
if (username.length < 3 || username.length > 20) {
alert('Username must be between 3 and 20 characters');
return;
}
setLoading(true);
try {
await onLogin(username.trim());
} catch (error) {
alert('Login failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="login-screen">
<div className="login-box">
<h1>💬 ChatApp</h1>
<p>Connect with friends instantly</p>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="Enter your username (samuel or alice)"
value={username}
onChange={(e) => setUsername(e.target.value)}
maxLength={20}
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'Connecting...' : 'Start Chatting'}
</button>
</form>
<div className="info">Try: samuel or alice</div>
</div>
</div>
);
};
const MainScreen = ({ currentUser, conversations, onStartChat, onOpenChat, onLogout }) => {
const [friendUsername, setFriendUsername] = useState('');
const handleStartChat = (e) => {
e.preventDefault();
if (!friendUsername.trim()) {
alert('Please enter a username');
return;
}
if (friendUsername === currentUser.userID) {
alert('You cannot chat with yourself');
return;
}
onStartChat(friendUsername.trim());
setFriendUsername('');
};
return (
<div className="main-screen">
<div className="header">
<h2>💬 ChatApp</h2>
<div className="user-info">
<span>Online as {currentUser.userName}</span>
<button onClick={onLogout}>Logout</button>
</div>
</div>
<div className="new-chat-section">
<h3>Start New Chat</h3>
<form onSubmit={handleStartChat} className="search-box">
<input
type="text"
placeholder="Enter username to chat with"
value={friendUsername}
onChange={(e) => setFriendUsername(e.target.value)}
/>
<button type="submit">Chat</button>
</form>
</div>
<div className="conversations">
<h3>Your Conversations</h3>
<div className="chat-list">
{conversations.length === 0 ? (
<div className="empty-message">No conversations yet. Start a new chat above!</div>
) : (
conversations.map((conversation) => (
<div
key={conversation.id}
className="chat-item"
onClick={() => onOpenChat(conversation)}
>
<div className="avatar">
{conversation.name.charAt(0).toUpperCase()}
</div>
<div className="chat-info">
<h4>{conversation.name}</h4>
<p>{conversation.lastMessage}</p>
</div>
</div>
))
)}
</div>
</div>
</div>
);
};
const ChatScreen = ({ currentChat, messages, onBack, onSendMessage, onVideoCall }) => {
const [messageText, setMessageText] = useState('');
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSendMessage = (e) => {
e.preventDefault();
if (!messageText.trim()) return;
onSendMessage(messageText.trim());
setMessageText('');
};
return (
<div className="chat-screen">
<div className="chat-header">
<button onClick={onBack}>← Back</button>
<div className="chat-info">
<h3>{currentChat.name}</h3>
<span>Online</span>
</div>
<button onClick={onVideoCall}>📹 Video</button>
</div>
<div className="messages-area">
{messages.map((message, index) => (
<div key={index} className={`message ${message.isOwn ? 'own' : ''}`}>
<div className="message-bubble">
{message.content}
</div>
<div className="message-time">
{new Date(message.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSendMessage} className="message-input">
<input
type="text"
placeholder="Type your message..."
value={messageText}
onChange={(e) => setMessageText(e.target.value)}
maxLength={500}
/>
<button type="submit">Send</button>
</form>
</div>
);
};
const VideoScreen = ({ currentChat, onEndCall }) => {
const localVideoRef = useRef(null);
const remoteVideoRef = useRef(null);
return (
<div className="video-screen">
<div className="video-container">
<div className="remote-video" ref={remoteVideoRef}>
<div className="status">Connecting...</div>
</div>
<div className="local-video" ref={localVideoRef}></div>
<div className="call-controls">
<button>🎤</button>
<button>📹</button>
<button onClick={onEndCall}>📞 End</button>
</div>
</div>
</div>
);
};
function App() {
const [currentScreen, setCurrentScreen] = useState('login');
const [currentUser, setCurrentUser] = useState(null);
const [currentChat, setCurrentChat] = useState(null);
const [conversations, setConversations] = useState([]);
const [messages, setMessages] = useState([]);
const [zim, setZim] = useState(null);
const [zg, setZg] = useState(null);
const [localStream, setLocalStream] = useState(null);
const handleLogin = async (username) => {
try {
console.log('Logging in user:', username);
const zimInstance = ZIM.create({ appID: CONFIG.appID });
if (!zimInstance) {
throw new Error('Failed to create ZIM instance');
}
zimInstance.on('peerMessageReceived', handleIncomingMessage);
zimInstance.on('roomMessageReceived', handleIncomingMessage); // Add room message listener
zimInstance.on('connectionStateChanged', handleConnectionChange);
zimInstance.on('error', (zim, errorInfo) => {
console.error('ZIM Error:', errorInfo);
});
const loginConfig = {
userName: username,
token: generateToken(username)
};
console.log('Logging in with token authentication...');
await zimInstance.login(username, loginConfig);
const zgInstance = new ZegoExpressEngine(CONFIG.appID, CONFIG.server);
zgInstance.on('roomStreamUpdate', handleStreamUpdate);
zgInstance.on('roomStateUpdate', (roomID, state, errorCode, extendedData) => {
console.log('Room state update:', state, errorCode);
});
setZim(zimInstance);
setZg(zgInstance);
setCurrentUser({ userID: username, userName: username });
setCurrentScreen('main');
console.log('Login successful');
} catch (error) {
console.error('Login failed:', error);
throw error;
}
};
const handleIncomingMessage = (zim, data) => {
// Handle both peer and room messages
const messageList = data.messageList || data;
const fromID = data.fromConversationID || data.fromRoomID || data.fromUserID;
console.log('Received messages from:', fromID);
if (!messageList || !Array.isArray(messageList)) return;
messageList.forEach(message => {
// Handle both text messages (type 1) and command messages (type 2)
const messageContent = message.message || message.data || '';
setConversations(prev => {
const exists = prev.find(conv => conv.id === fromID);
if (!exists) {
return [...prev, {
id: fromID,
name: fromID,
lastMessage: messageContent,
timestamp: Date.now()
}];
}
return prev.map(conv =>
conv.id === fromID
? { ...conv, lastMessage: messageContent, timestamp: Date.now() }
: conv
);
});
if (currentChat && currentChat.id === fromID) {
setMessages(prev => [...prev, {
content: messageContent,
timestamp: Date.now(),
isOwn: false
}]);
}
});
};
const handleConnectionChange = (zim, { state, event }) => {
console.log('Connection state:', state, event);
if (state === 0 && event === 3) {
console.log('Connection lost, may need to reconnect...');
}
};
const handleStartChat = (friendUsername) => {
const newConversation = {
id: friendUsername,
name: friendUsername,
lastMessage: 'No messages yet',
timestamp: Date.now()
};
setConversations(prev => {
const exists = prev.find(conv => conv.id === friendUsername);
if (!exists) {
return [...prev, newConversation];
}
return prev;
});
setCurrentChat(newConversation);
setMessages([]);
// Join room for Signaling plan messaging
if (zim) {
const roomID = `chat_${currentUser.userID}_${friendUsername}`;
zim.enterRoom({ roomID: roomID, roomName: `Chat with ${friendUsername}` })
.then(() => {
console.log('Joined room:', roomID);
})
.catch((error) => {
console.log('Room join failed, continuing anyway:', error);
});
}
setCurrentScreen('chat');
};
const handleOpenChat = (conversation) => {
setCurrentChat(conversation);
setMessages([]);
setCurrentScreen('chat');
};
const handleSendMessage = async (messageText) => {
if (!currentChat || !zim) return;
try {
// Use Command message (type: 2) for Signaling plan instead of Text message (type: 1). Choose other choice depending on your plan.
const messageObj = {
type: 2, // ZIMCommandMessage for signaling plan
message: messageText,
extendedData: ''
};
const config = {
priority: 1
};
const notification = {
onMessageAttached: function(message) {
console.log('Message attached:', message);
}
};
// Use room messages for Signaling plan
const roomID = `chat_${currentUser.userID}_${currentChat.id}`;
await zim.sendMessage(messageObj, roomID, 1, config, notification);
setMessages(prev => [...prev, {
content: messageText,
timestamp: Date.now(),
isOwn: true
}]);
setConversations(prev =>
prev.map(conv =>
conv.id === currentChat.id
? { ...conv, lastMessage: messageText, timestamp: Date.now() }
: conv
)
);
console.log('Message sent successfully');
} catch (error) {
console.error('Failed to send message:', error);
alert('Failed to send message');
}
};
const handleVideoCall = async () => {
if (!currentChat || !zg) {
alert('No active chat');
return;
}
try {
const roomID = `call_${currentUser.userID}_${currentChat.id}_${Date.now()}`;
console.log('Starting video call in room:', roomID);
await zg.loginRoom(roomID, {
userID: currentUser.userID,
userName: currentUser.userName
});
const stream = await zg.createStream({
camera: { audio: true, video: true }
});
stream.play("local-video");
await zg.startPublishingStream(currentUser.userID, stream);
setLocalStream(stream);
setCurrentScreen('video');
console.log('Video call started');
} catch (error) {
console.error('Failed to start video call:', error);
alert('Failed to start video call');
}
};
const handleStreamUpdate = async (roomID, updateType, streamList) => {
if (updateType === 'ADD' && streamList.length > 0) {
console.log('Remote stream added');
const remoteStream = await zg.startPlayingStream(streamList[0].streamID);
remoteStream.play("remote-video");
} else if (updateType === 'DELETE') {
console.log('Remote stream removed');
setTimeout(() => handleEndCall(), 2000);
}
};
const handleEndCall = async () => {
try {
if (localStream && zg) {
await zg.stopPublishingStream(currentUser.userID);
zg.destroyStream(localStream);
setLocalStream(null);
}
if (zg) {
await zg.logoutRoom();
}
setCurrentScreen('chat');
console.log('Video call ended');
} catch (error) {
console.error('Error ending call:', error);
}
};
const handleLogout = () => {
if (confirm('Are you sure you want to logout?')) {
if (zim) {
zim.logout();
zim.destroy();
}
if (zg) {
zg.destroyEngine();
}
setCurrentUser(null);
setCurrentChat(null);
setConversations([]);
setMessages([]);
setZim(null);
setZg(null);
setCurrentScreen('login');
}
};
const renderScreen = () => {
switch (currentScreen) {
case 'login':
return <LoginScreen onLogin={handleLogin} />;
case 'main':
return (
<MainScreen
currentUser={currentUser}
conversations={conversations}
onStartChat={handleStartChat}
onOpenChat={handleOpenChat}
onLogout={handleLogout}
/>
);
case 'chat':
return (
<ChatScreen
currentChat={currentChat}
messages={messages}
onBack={() => setCurrentScreen('main')}
onSendMessage={handleSendMessage}
onVideoCall={handleVideoCall}
/>
);
case 'video':
return (
<VideoScreen
currentChat={currentChat}
onEndCall={handleEndCall}
/>
);
default:
return <LoginScreen onLogin={handleLogin} />;
}
};
return (
<div className="app">
{renderScreen()}
</div>
);
}
export default App;
This React component structure provides:
-
LoginScreen
: Handles user authentication with username. -
MainScreen
: Shows conversations and allows starting new chats. -
ChatScreen
: Individual messaging interface with video call option. -
VideoScreen
: Full-screen video calling experience. -
App
: Main component managing state and screen navigation.
5. Style Your React App
Replace the contents of src/App.css
with modern, clean styling:
/* src/App.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
}
.app {
height: 100vh;
width: 100vw;
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #8b5cf6 100%);
overflow: hidden;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem; /* Add some padding for better spacing */
}
.app::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 20% 80%, rgba(255,255,255,0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255,255,255,0.1) 0%, transparent 50%);
pointer-events: none;
}
/* Login Screen */
.login-screen {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
padding: 2rem;
}
.login-box {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 3rem;
border-radius: 24px;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.15),
0 0 0 1px rgba(255,255,255,0.2);
min-width: 380px;
max-width: 450px;
width: 100%;
transform: translateY(0);
transition: all 0.3s ease;
border: 1px solid rgba(255,255,255,0.2);
}
.login-box:hover {
transform: translateY(-5px);
box-shadow: 0 30px 80px rgba(0,0,0,0.2);
}
.login-box h1 {
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
font-size: 3rem;
font-weight: 700;
}
.login-box p {
color: #6b7280;
margin-bottom: 2.5rem;
font-size: 1.1rem;
}
.login-box form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.login-box input {
padding: 18px 20px;
border: 2px solid #e5e7eb;
border-radius: 16px;
font-size: 16px;
transition: all 0.3s ease;
background: rgba(255,255,255,0.8);
}
.login-box input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
background: rgba(255,255,255,1);
transform: translateY(-1px);
}
.login-box button {
padding: 18px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.login-box button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4);
}
.login-box button:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.info {
font-size: 0.9rem;
color: #9ca3af;
margin-top: 1.5rem;
font-weight: 500;
}
/* Main Screen */
.main-screen {
background: rgba(248, 250, 252, 0.95);
backdrop-filter: blur(20px);
height: 95vh;
max-height: 800px; /* Add max height */
display: flex;
flex-direction: column;
border-radius: 24px;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.2);
width: 100%;
max-width: 450px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
margin: 0 auto; /* Center the container */
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border-bottom: 1px solid rgba(229, 231, 235, 0.8);
}
.header h2 {
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
font-size: 1.8rem;
}
.user-info {
display: flex;
align-items: center;
gap: 1.5rem;
}
.user-info span {
font-weight: 600;
color: #374151;
}
.user-info button {
padding: 10px 20px;
background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
color: #374151;
border: none;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.user-info button:hover {
background: linear-gradient(135deg, #e5e7eb, #d1d5db);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.new-chat-section {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
margin: 1.5rem;
padding: 2rem;
border-radius: 20px;
box-shadow: 0 8px 25px rgba(0,0,0,0.08);
border: 1px solid rgba(255,255,255,0.5);
}
.new-chat-section h3 {
margin-bottom: 1.5rem;
color: #374151;
font-weight: 700;
font-size: 1.2rem;
}
.search-box {
display: flex;
gap: 1rem;
}
.search-box input {
flex: 1;
padding: 16px 20px;
border: 2px solid #e5e7eb;
border-radius: 16px;
font-size: 16px;
transition: all 0.3s ease;
background: rgba(255,255,255,0.8);
}
.search-box input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
background: rgba(255,255,255,1);
}
.search-box button {
padding: 16px 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 16px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.search-box button:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.conversations {
flex: 1;
padding: 0 1.5rem 1.5rem;
overflow-y: auto;
}
.conversations h3 {
margin-bottom: 1.5rem;
color: #374151;
font-weight: 700;
font-size: 1.2rem;
}
.chat-list {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 8px 25px rgba(0,0,0,0.08);
min-height: 200px;
border: 1px solid rgba(255,255,255,0.5);
overflow: hidden;
}
.empty-message {
padding: 3rem 2rem;
text-align: center;
color: #9ca3af;
font-size: 1.1rem;
font-weight: 500;
}
.chat-item {
padding: 1.5rem 2rem;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
cursor: pointer;
display: flex;
align-items: center;
gap: 1.5rem;
transition: all 0.3s ease;
position: relative;
}
.chat-item:hover {
background: rgba(102, 126, 234, 0.05);
transform: translateX(4px);
}
.chat-item:last-child {
border-bottom: none;
}
.avatar {
width: 52px;
height: 52px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 1.3rem;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.chat-info h4 {
margin-bottom: 0.3rem;
color: #374151;
font-weight: 600;
font-size: 1.1rem;
}
.chat-info p {
color: #9ca3af;
font-size: 0.95rem;
line-height: 1.4;
}
/* Chat Screen */
.chat-screen {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
height: 95vh;
max-height: 800px; /* Add max height */
display: flex;
flex-direction: column;
border-radius: 24px;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.2);
width: 100%;
max-width: 450px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
margin: 0 auto; /* Center the container */
}
.chat-header {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
}
.chat-header button {
background: rgba(255,255,255,0.2);
color: white;
border: none;
padding: 12px 16px;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.3);
}
.chat-header button:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-1px);
}
.chat-info h3 {
margin-bottom: 0.3rem;
font-weight: 700;
font-size: 1.3rem;
}
.chat-info span {
font-size: 0.9rem;
opacity: 0.9;
font-weight: 500;
}
.messages-area {
flex: 1;
padding: 2rem;
overflow-y: auto;
background: linear-gradient(180deg, rgba(248, 250, 252, 0.8) 0%, rgba(241, 245, 249, 0.8) 100%);
}
.message {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
align-items: flex-start;
animation: messageSlideIn 0.3s ease;
}
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.own {
align-items: flex-end;
}
.message-bubble {
max-width: 75%;
padding: 16px 20px;
border-radius: 24px;
word-wrap: break-word;
position: relative;
transition: all 0.3s ease;
line-height: 1.5;
font-size: 0.95rem;
}
.message.own .message-bubble {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
border-bottom-right-radius: 8px;
}
.message:not(.own) .message-bubble {
background: rgba(255, 255, 255, 0.95);
color: #374151;
border: 1px solid rgba(229, 231, 235, 0.8);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
border-bottom-left-radius: 8px;
backdrop-filter: blur(10px);
}
.message-bubble:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
}
.message.own .message-bubble:hover {
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.message-time {
font-size: 0.75rem;
opacity: 0.7;
margin-top: 0.5rem;
font-weight: 500;
color: #9ca3af;
}
.message-input {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
padding: 1.5rem 2rem;
display: flex;
gap: 1rem;
border-top: 1px solid rgba(229, 231, 235, 0.8);
box-shadow: 0 -4px 20px rgba(0,0,0,0.05);
}
.message-input input {
flex: 1;
padding: 16px 24px;
border: 2px solid #e5e7eb;
border-radius: 24px;
outline: none;
font-size: 16px;
transition: all 0.3s ease;
background: rgba(255,255,255,0.8);
}
.message-input input:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
background: rgba(255,255,255,1);
}
.message-input button {
padding: 16px 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 24px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.message-input button:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
/* Video Call Screen */
.video-screen {
background: linear-gradient(135deg, #000 0%, #1a1a1a 100%);
height: 95vh;
max-height: 800px; /* Add max height */
position: relative;
border-radius: 24px;
overflow: hidden;
width: 100%;
max-width: 450px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
margin: 0 auto; /* Center the container */
}
.video-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.remote-video {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1f2937, #374151);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.3rem;
font-weight: 600;
}
.local-video {
position: absolute;
top: 24px;
right: 24px;
width: 220px;
height: 165px;
background: linear-gradient(135deg, #374151, #4b5563);
border-radius: 16px;
border: 3px solid rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
.call-controls {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 1.5rem;
background: rgba(0,0,0,0.4);
padding: 1rem 1.5rem;
border-radius: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.1);
}
.call-controls button {
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
color: white;
font-size: 1.3rem;
cursor: pointer;
background: rgba(255,255,255,0.2);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.3);
}
.call-controls button:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(255,255,255,0.2);
}
.call-controls button:last-child {
background: linear-gradient(135deg, #dc2626, #ef4444);
border: 1px solid rgba(220, 38, 38, 0.5);
}
.call-controls button:last-child:hover {
background: linear-gradient(135deg, #b91c1c, #dc2626);
box-shadow: 0 4px 15px rgba(220, 38, 38, 0.4);
}
/* Scrollbar Styling */
.conversations::-webkit-scrollbar,
.messages-area::-webkit-scrollbar {
width: 8px;
}
.conversations::-webkit-scrollbar-track,
.messages-area::-webkit-scrollbar-track {
background: rgba(0,0,0,0.05);
border-radius: 4px;
}
.conversations::-webkit-scrollbar-thumb,
.messages-area::-webkit-scrollbar-thumb {
background: rgba(102, 126, 234, 0.3);
border-radius: 4px;
transition: all 0.3s ease;
}
.conversations::-webkit-scrollbar-thumb:hover,
.messages-area::-webkit-scrollbar-thumb:hover {
background: rgba(102, 126, 234, 0.5);
}
/* Responsive Design */
@media (min-width: 1200px) {
.main-screen,
.chat-screen,
.video-screen {
max-width: 500px;
}
}
@media (max-width: 768px) {
.app {
align-items: stretch;
padding: 0; /* Remove padding on mobile */
}
.main-screen,
.chat-screen,
.video-screen {
height: 100vh;
max-width: 100%;
border-radius: 0;
box-shadow: none;
margin: 0; /* Remove margin on mobile */
}
.video-container {
height: 100vh;
}
.login-box {
margin: 1rem;
min-width: auto;
padding: 2rem;
}
.header {
padding: 1rem 1.5rem;
}
.user-info {
flex-direction: column;
gap: 0.5rem;
}
.new-chat-section {
margin: 1rem;
padding: 1.5rem;
}
.local-video {
width: 140px;
height: 105px;
top: 16px;
right: 16px;
}
.call-controls {
gap: 1rem;
padding: 0.75rem 1rem;
}
.call-controls button {
width: 48px;
height: 48px;
font-size: 1.1rem;
}
.search-box {
flex-direction: column;
gap: 1rem;
}
.message-bubble {
max-width: 85%;
}
.messages-area {
padding: 1.5rem;
}
.message-input {
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.login-box h1 {
font-size: 2.5rem;
}
.header h2 {
font-size: 1.5rem;
}
.avatar {
width: 44px;
height: 44px;
font-size: 1.1rem;
}
.chat-item {
padding: 1rem 1.5rem;
}
}
This CSS provides:
- Modern React-friendly styling with component-based organization.
- Clean, professional design with blue/purple gradients.
- Responsive layout that works on phones and computers.
- Smooth animations and hover effects.
- Optimized for React's component structure.
6. Run Your React Messaging App
Now start your development server:
npm run dev
This will start Vite's development server and open your app at http://localhost:5173
(or another port if 5173
is busy).
Testing Your App:
- Login Test: Enter a username and click "Start Chatting"
Messaging Test:
- Open another browser window/tab to the same URL.
- Login with a different username.
- Start a chat between the two users.
- Send messages back and forth.
*Video Call Test: *
- Click the video button in a chat to start a video call
Curious how everything comes together? Get the full demo code here to test it yourself:
Conclusion
You've successfully built a complete messaging app with real-time chat and video calling features! Using ZEGOCLOUD's powerful SDKs, you created an app that rivals popular messaging platforms like Kik - all without requiring phone numbers or complex backend infrastructure. Your users can now chat instantly, share messages in real-time, and connect face-to-face through video calls.
This foundation gives you endless possibilities for growth. Consider adding group chats, file sharing, push notifications, or custom themes to make your app unique. With ZEGOCLOUD handling the heavy technical lifting, you can focus on building features that matter to your users. Deploy your app, gather feedback, and watch your messaging platform come to life!
Top comments (0)