OdysseyUI

Chat Container

A composable chat container with auto-scroll, animated messages, empty state, scroll-to-bottom button, and divider support.

Loading...

Note: For rendering individual messages with avatars, bubble styling, and timestamps, check out the Message Bubble component — it's designed to work seamlessly inside ChatContainerMessage.

Installation

Usage

import {
  ChatContainer,
  ChatContainerContent,
  ChatContainerMessage,
  ChatContainerEmptyState,
  ChatContainerDivider,
} from '@/components/odysseyui/chat-container';

export default function Page() {
  return (
    <ChatContainer className="h-[500px] w-full max-w-3xl">
      <ChatContainerContent>
        <ChatContainerEmptyState title="Ask me anything" />
        <ChatContainerDivider label="Today" />
        <ChatContainerMessage>
          <div className="flex justify-end">
            <div className="bg-primary text-primary-foreground rounded-2xl px-4 py-2 text-sm max-w-xs">
              Hello!
            </div>
          </div>
        </ChatContainerMessage>
      </ChatContainerContent>
    </ChatContainer>
  );
}

How It Works

  • ChatContainer wraps everything in a double-ring card shell with a ScrollArea viewport internally tracked via viewportRef.
  • Auto-scroll: when autoScroll is true (default), the viewport scrolls to the bottom whenever a new message is registered — but only if the user was already at the bottom.
  • ChatContainerMessage calls registerMessage() on mount and unregisters on unmount, keeping an internal messageCount that powers empty-state detection and auto-scroll triggers.
  • ChatContainerEmptyState renders only when messageCount === 0, animating in/out with a fade+slide.
  • ChatContainerScrollButton floats above the scroll area and appears with an AnimatePresence transition whenever the user scrolls up past the bottomThreshold.
  • ChatContainerDivider renders a full-width horizontal rule with an optional centred label (e.g. "Today").

API Reference

ChatContainer

PropTypeDefault
bottomThreshold?
number
64
autoScroll?
boolean
true
children
React.ReactNode
-

ChatContainerContent

Centres and pads its children. Pass className to override max-w-3xl or spacing.

ChatContainerEmptyState

PropTypeDefault
title?
string
"No messages yet"
description?
string
"Start a conversation to see messages here."
icon?
React.ReactNode
-

ChatContainerMessage

Wraps each individual message. Animates in with a fade + upward slide and registers itself with the container context so auto-scroll and empty-state detection work correctly.

ChatContainerScrollButton

Floats over the bottom of the scroll area, visible only when the user has scrolled away from the bottom. Clicking it smooth-scrolls to the latest message. Accepts all Button props plus an optional icon override.

ChatContainerDivider

PropTypeDefault
label?
string
-

useChatContainer

Hook that exposes the container context: scrollToBottom, isAtBottom, isEmpty, messageCount, registerMessage. Must be called inside a <ChatContainer />.

Built by Shr3kx & iam-sahil. The source code is available on GitHub.

Last updated: 3/27/2026