BuildingLesson 3 of 11

Building Message Bubbles in React

4 min readConversational UI for DesignersUpdated Apr 2, 2026

A message bubble in React is a styled div inside a scrollable list, rendered from a { role, content, timestamp } data shape. This lesson builds one from scratch with auto-scroll, avatars, and status states: the foundation under every chat UI.

Data Model

Start with a clean message type:

Message type definition
interface Message {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  timestamp: Date;
  status?: 'sending' | 'sent' | 'error';
}

Keep the model simple. You can extend it later with attachments, metadata, or citations, but the core is: who said what, and when.

Message Component

Each message is a flex container with conditional alignment:

ChatMessage component
  • max-w-[75%] prevents full-width blobs that are hard to read
  • rounded-2xl gives the modern bubble shape
  • whitespace-pre-wrap preserves AI formatting and line breaks

Auto-Scrolling

Chat interfaces must auto-scroll to the latest message - but only when the user is already at the bottom. If they've scrolled up to read history, don't yank them down.

Smart auto-scroll hook
const messagesEndRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);

const isNearBottom = () => {
  const el = containerRef.current;
  if (!el) return true;
  return el.scrollHeight - el.scrollTop - el.clientHeight < 100;
};

useEffect(() => {
  if (isNearBottom()) {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }
}, [messages]);

The < 100 threshold gives a small buffer - users scrolled up slightly still get auto-scroll.

Test auto-scroll on mobile especially. Touch scrolling has momentum - users may still be "scrolling" when a new message arrives. Use `behavior: 'smooth'` to avoid jarring jumps.