Display connected users with customizable avatars and user information. Shows who’s currently active in your collaborative session.
ConnectedUsers
component displays a visual representation of all users currently connected to your collaboration session. It shows user avatars in an attractive group layout with automatic overflow handling, making it perfect for headers, sidebars, or any area where you want to show team presence.
import { ConnectedUsers } from 'react-together'
function AppHeader() {
return (
<header className="app-header">
<h1>My Collaborative App</h1>
<div className="user-presence">
<ConnectedUsers maxAvatars={5} />
</div>
</header>
)
}
import { ConnectedUsers, useIsTogether } from 'react-together'
function ApplicationHeader() {
const isTogether = useIsTogether()
return (
<header className="app-header">
<div className="header-left">
<h1>Team Workspace</h1>
<div className="breadcrumb">
<span>Project</span>
<span>•</span>
<span>Document</span>
</div>
</div>
<div className="header-right">
<div className="collaboration-status">
{isTogether ? (
<div className="connected-section">
<span className="status-text">👥 Collaborating</span>
<ConnectedUsers maxAvatars={4} />
</div>
) : (
<div className="disconnected-section">
<span className="status-text">🔴 Offline</span>
</div>
)}
</div>
<div className="header-actions">
<button className="header-btn">Save</button>
<button className="header-btn">Share</button>
</div>
</div>
</header>
)
}
import { ConnectedUsers, useConnectedUsers } from 'react-together'
function UserSidebar() {
const allUsers = useConnectedUsers()
return (
<div className="user-sidebar">
<div className="sidebar-header">
<h3>Team Members</h3>
<div className="user-count">
{allUsers.length} online
</div>
</div>
<div className="user-avatars">
<ConnectedUsers maxAvatars={6} />
</div>
<div className="user-list">
<h4>All Members</h4>
<div className="user-details">
{allUsers.map(user => (
<div key={user.userId} className="user-item">
<div className="user-avatar-small">
{user.nickname ? user.nickname[0].toUpperCase() : '?'}
</div>
<div className="user-info">
<span className="user-name">
{user.nickname || `User ${user.userId.slice(0, 8)}`}
</span>
<span className="user-status">
🟢 Active
</span>
</div>
</div>
))}
</div>
</div>
</div>
)
}
import { ConnectedUsers, useConnectedUsers, useIsTogether } from 'react-together'
function Dashboard() {
const connectedUsers = useConnectedUsers()
const isTogether = useIsTogether()
return (
<div className="dashboard">
<div className="dashboard-header">
<h1>Project Dashboard</h1>
<div className="dashboard-stats">
<div className="stat-card">
<div className="stat-value">{connectedUsers.length}</div>
<div className="stat-label">Active Users</div>
</div>
<div className="stat-card">
<div className="stat-value">{isTogether ? '✅' : '❌'}</div>
<div className="stat-label">Connected</div>
</div>
</div>
</div>
<div className="dashboard-content">
<div className="content-section">
<div className="section-header">
<h2>Current Session</h2>
<div className="session-users">
<ConnectedUsers maxAvatars={8} />
</div>
</div>
<div className="session-activity">
<h3>Recent Activity</h3>
<div className="activity-list">
<div className="activity-item">
<span className="activity-time">2 min ago</span>
<span className="activity-text">User joined the session</span>
</div>
<div className="activity-item">
<span className="activity-time">5 min ago</span>
<span className="activity-text">Document was updated</span>
</div>
</div>
</div>
</div>
<div className="content-section">
<h2>Workspace</h2>
<div className="workspace-content">
<p>Collaborative workspace content...</p>
</div>
</div>
</div>
</div>
)
}
import { ConnectedUsers, useConnectedUsers } from 'react-together'
import { useState } from 'react'
function FloatingUserIndicator() {
const connectedUsers = useConnectedUsers()
const [isExpanded, setIsExpanded] = useState(false)
return (
<div className="floating-user-indicator">
<div
className="indicator-trigger"
onClick={() => setIsExpanded(!isExpanded)}
>
<ConnectedUsers maxAvatars={3} />
<span className="user-count-badge">
{connectedUsers.length}
</span>
</div>
{isExpanded && (
<div className="indicator-popup">
<div className="popup-header">
<h4>Connected Users</h4>
<button onClick={() => setIsExpanded(false)}>
×
</button>
</div>
<div className="popup-content">
<div className="user-grid">
{connectedUsers.map(user => (
<div key={user.userId} className="user-card">
<div className="user-avatar">
{user.nickname ? user.nickname[0].toUpperCase() : '?'}
</div>
<div className="user-details">
<div className="user-name">
{user.nickname || `User ${user.userId.slice(0, 8)}`}
</div>
<div className="user-status">
🟢 Online
</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
)
}
import { ConnectedUsers, useConnectedUsers, useIsTogether } from 'react-together'
function CollaborationToolbar() {
const connectedUsers = useConnectedUsers()
const isTogether = useIsTogether()
return (
<div className="collaboration-toolbar">
<div className="toolbar-section">
<h3>Real-time Collaboration</h3>
<div className="collaboration-status">
{isTogether ? (
<div className="status-active">
<span className="status-indicator">🟢</span>
<span className="status-text">Live Session</span>
</div>
) : (
<div className="status-inactive">
<span className="status-indicator">🔴</span>
<span className="status-text">Offline</span>
</div>
)}
</div>
</div>
<div className="toolbar-section">
<div className="users-section">
<div className="users-header">
<span className="users-label">Team ({connectedUsers.length})</span>
</div>
<div className="users-display">
<ConnectedUsers maxAvatars={5} />
</div>
</div>
</div>
<div className="toolbar-section">
<div className="toolbar-actions">
<button className="toolbar-btn">
📝 Comments
</button>
<button className="toolbar-btn">
🔗 Share
</button>
<button className="toolbar-btn">
💬 Chat
</button>
</div>
</div>
</div>
)
}
import { ConnectedUsers, useConnectedUsers, useStateTogether } from 'react-together'
function GameLobby() {
const connectedUsers = useConnectedUsers()
const [gameState, setGameState] = useStateTogether('game', {
maxPlayers: 4,
gameStarted: false
})
const canStartGame = connectedUsers.length >= 2 && connectedUsers.length <= gameState.maxPlayers
return (
<div className="game-lobby">
<div className="lobby-header">
<h1>🎮 Game Lobby</h1>
<div className="lobby-status">
<div className="player-count">
{connectedUsers.length} / {gameState.maxPlayers} players
</div>
<div className="lobby-state">
{gameState.gameStarted ? '🎯 In Game' : '⏳ Waiting'}
</div>
</div>
</div>
<div className="lobby-content">
<div className="players-section">
<h2>Players</h2>
<div className="players-display">
<ConnectedUsers maxAvatars={gameState.maxPlayers} />
</div>
<div className="player-slots">
{Array.from({ length: gameState.maxPlayers }).map((_, index) => {
const user = connectedUsers[index]
return (
<div
key={index}
className={`player-slot ${user ? 'occupied' : 'empty'}`}
>
{user ? (
<div className="player-info">
<div className="player-avatar">
{user.nickname ? user.nickname[0].toUpperCase() : '?'}
</div>
<span className="player-name">
{user.nickname || `Player ${user.userId.slice(0, 6)}`}
</span>
<span className="player-status">🟢 Ready</span>
</div>
) : (
<div className="empty-slot">
<div className="empty-avatar">?</div>
<span className="empty-text">Waiting for player...</span>
</div>
)}
</div>
)
})}
</div>
</div>
<div className="lobby-controls">
<button
onClick={() => setGameState(prev => ({ ...prev, gameStarted: true }))}
disabled={!canStartGame}
className="start-game-btn"
>
{canStartGame ? 'Start Game' : 'Need 2-4 players'}
</button>
</div>
</div>
</div>
)
}
import { ConnectedUsers, useConnectedUsers } from 'react-together'
import { useState } from 'react'
function MeetingRoom() {
const connectedUsers = useConnectedUsers()
const [isMuted, setIsMuted] = useState(false)
const [isVideoOn, setIsVideoOn] = useState(true)
return (
<div className="meeting-room">
<div className="meeting-header">
<h1>📹 Team Meeting</h1>
<div className="meeting-info">
<span className="meeting-time">
Started 15 minutes ago
</span>
<span className="participant-count">
{connectedUsers.length} participants
</span>
</div>
</div>
<div className="meeting-content">
<div className="video-grid">
<div className="main-video">
<div className="video-placeholder">
<h3>Main Screen</h3>
<p>Screen sharing or presentation view</p>
</div>
</div>
<div className="participant-videos">
{connectedUsers.slice(0, 6).map(user => (
<div key={user.userId} className="participant-video">
<div className="video-placeholder">
<div className="participant-avatar">
{user.nickname ? user.nickname[0].toUpperCase() : '?'}
</div>
<span className="participant-name">
{user.nickname || `User ${user.userId.slice(0, 6)}`}
</span>
</div>
</div>
))}
</div>
</div>
</div>
<div className="meeting-controls">
<div className="controls-left">
<button
onClick={() => setIsMuted(!isMuted)}
className={`control-btn ${isMuted ? 'muted' : ''}`}
>
{isMuted ? '🔇' : '🎤'}
</button>
<button
onClick={() => setIsVideoOn(!isVideoOn)}
className={`control-btn ${!isVideoOn ? 'video-off' : ''}`}
>
{isVideoOn ? '📹' : '📵'}
</button>
</div>
<div className="controls-center">
<div className="participants-indicator">
<ConnectedUsers maxAvatars={6} />
</div>
</div>
<div className="controls-right">
<button className="control-btn">
💬 Chat
</button>
<button className="control-btn">
🔗 Share
</button>
<button className="control-btn end-call">
📞 End Call
</button>
</div>
</div>
</div>
)
}
import { ConnectedUsers, useConnectedUsers } from 'react-together'
import { useState, useEffect } from 'react'
function ResponsiveUserDisplay() {
const connectedUsers = useConnectedUsers()
const [screenSize, setScreenSize] = useState<'small' | 'medium' | 'large'>('large')
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth
if (width < 768) {
setScreenSize('small')
} else if (width < 1024) {
setScreenSize('medium')
} else {
setScreenSize('large')
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const getMaxAvatars = () => {
switch (screenSize) {
case 'small': return 2
case 'medium': return 4
case 'large': return 6
default: return 3
}
}
return (
<div className="responsive-user-display">
<div className="user-section">
<h3>
{screenSize === 'small' ? 'Team' : 'Team Members'}
</h3>
<div className="user-display">
<ConnectedUsers maxAvatars={getMaxAvatars()} />
</div>
{screenSize !== 'small' && (
<div className="user-details">
<span className="user-count">
{connectedUsers.length} member{connectedUsers.length !== 1 ? 's' : ''} online
</span>
</div>
)}
</div>
{screenSize === 'large' && (
<div className="detailed-user-list">
<h4>All Members</h4>
<div className="user-list">
{connectedUsers.map(user => (
<div key={user.userId} className="user-item">
<div className="user-avatar-small">
{user.nickname ? user.nickname[0].toUpperCase() : '?'}
</div>
<span className="user-name">
{user.nickname || `User ${user.userId.slice(0, 8)}`}
</span>
<span className="user-status">🟢</span>
</div>
))}
</div>
</div>
)}
</div>
)
}
import { ConnectedUsers } from 'react-together'
function DebugUserDisplay() {
return (
<div className="debug-container">
<h2>Debug Mode - User Display</h2>
<div className="debug-section">
<h3>Normal Display</h3>
<ConnectedUsers maxAvatars={4} />
</div>
<div className="debug-section">
<h3>Debug Mode (Development Only)</h3>
<ConnectedUsers maxAvatars={4} debug={true} />
</div>
</div>
)
}
/* Avatar group container */
.p-avatar-group {
display: flex;
align-items: center;
gap: 10px;
}
/* Individual avatars */
.p-avatar {
background-color: #3b82f6;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
/* Overflow indicator */
.p-avatar.overflow {
background-color: #6b7280;
}
/* Custom avatar colors */
.p-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Custom avatar sizes */
.p-avatar.custom-size {
width: 48px;
height: 48px;
font-size: 16px;
}
/* Custom group spacing */
.p-avatar-group.custom-spacing {
gap: 5px;
}
/* Custom overflow indicator */
.p-avatar.overflow {
background-color: #f59e0b;
color: white;
}
// ✅ Good - Reasonable max avatars
<ConnectedUsers maxAvatars={6} />
// ❌ Bad - Too many avatars can clutter UI
<ConnectedUsers maxAvatars={20} />
// ✅ Good - Clear context
<div className="team-section">
<h3>Team Members</h3>
<ConnectedUsers maxAvatars={5} />
</div>
// ✅ Good - Show user count
<div className="user-info">
<ConnectedUsers maxAvatars={4} />
<span>{connectedUsers.length} online</span>
</div>
// ✅ Good - Descriptive labels
<div className="user-presence" aria-label="Connected team members">
<ConnectedUsers maxAvatars={5} />
</div>
// ✅ Good - Responsive avatar count
const maxAvatars = isMobile ? 3 : 6
<ConnectedUsers maxAvatars={maxAvatars} />
SessionManager
- Manage collaboration sessionsChat
- Real-time team communicationHoverHighlighter
- Show user interactionsuseConnectedUsers
- The underlying hook powering ConnectedUsersuseMyId
- Identify current useruseNicknames
- Manage user display namesimport { ConnectedUsers } from 'react-together'
interface UserDisplayProps {
maxUsers?: number
showDebug?: boolean
}
function UserDisplay({ maxUsers = 4, showDebug = false }: UserDisplayProps) {
return (
<ConnectedUsers
maxAvatars={maxUsers}
debug={showDebug}
/>
)
}