This package provides a set of frontend components designed to help you quickly build modern, responsive chat interfaces. It focuses on flexibility, developer experience, and seamless integration with any backend.
-
Backend‑agnostic: Plug in any backend service via simple callbacks. The components don’t enforce a specific data source, making them compatible with REST APIs, WebSockets, GraphQL, Firebase, or custom infrastructures.
-
Tailwind CSS support: Built with Tailwind for clean, utility‑first defaults. Every component is fully customizable — override styles using Tailwind classes or apply your own custom CSS for complete control over the look and feel.
-
File handling: Out‑of‑the‑box support for sending and receiving files. Users can drag‑and‑drop, paste, or select files, and the UI provides previews for images and other file types.
-
Accessible & responsive: Components are designed with accessibility and responsiveness in mind, ensuring a smooth experience across devices and screen sizes.
-
Composable architecture: Each piece (Chat, ChatHeader, MessageList, MessageBar, etc.) is modular. You can use them together for a full chat app or individually to fit into existing layouts.
Install the package via npm:
npm install @alejotoro-o/chat-ui- React 19+ and React DOM 19+ are required as peer dependencies. Make sure your project is using compatible versions:
npm install react@^19.1.0 react-dom@^19.1.0- Tailwind CSS must be configured in your project. The components rely on Tailwind utilities for styling.
Update your tailwind.config.js to include the package so styles are applied correctly:
const config = {
content: [
// Your app sources
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
// Include chat-ui components
'./node_modules/@alejotoro-o/chat-ui/dist/*.{js,jsx,ts,tsx}',
],
// Rest of your Tailwind config...
};
export default config;- The package ships with ESM exports (
type: "module") and provides both JavaScript and TypeScript definitions (dist/index.jsanddist/index.d.ts). - Styling utilities use
clsxandtailwind-mergeinternally, so you can safely combine and override classes.
Once installed and configured, you can start importing components like:
import { Chat, ChatHeader, MessageList, MessageBar } from '@alejotoro-o/chat-ui';This package allows you to build a chat UI in a modular fashion. Each component is highly extensible and customizable, so you can compose them together to create a full chat interface or use them individually in existing layouts.
Below is a minimal example showing how to wire up the components with local state. For a more complete demo, check out App.tsx in the dev folder.
type ChatMessage = {
text?: string;
files?: { name: string; url: string; type: string }[];
sender: "UserA" | "UserB";
timestamp: Date;
};
const sendMessage = (payload: { text?: string; files?: File[] }, sender: "UserA" | "UserB") => {
const fileObjs = payload.files?.map((file) => ({
name: file.name,
type: file.type,
url: URL.createObjectURL(file),
}));
setMessages((prev) => [
...prev,
{
text: payload.text,
files: fileObjs,
sender,
timestamp: new Date(),
},
]);
};
const deleteChat = () => {
console.log("Chat deleted");
setMessages([]);
};
const clearChat = () => {
console.log("Chat cleared");
setMessages([]);
};
<Chat>
<ChatHeader
name="User A"
status="Online"
options={[
{ label: "Clear Chat", onClick: clearChat },
{ label: "Delete Chat", onClick: deleteChat, destructive: true },
]}
/>
<MessageList>
{messages.map((msg, idx) => {
const prevMsg = idx > 0 ? messages[idx - 1] : null;
const prevDate = prevMsg ? formatDate(prevMsg.timestamp) : null;
const currDate = formatDate(msg.timestamp);
return (
<React.Fragment key={idx}>
{prevDate !== currDate && <DateDivider date={msg.timestamp} />}
<Message
text={msg.text}
files={msg.files}
isMe={msg.sender === "UserA"}
timestamp={msg.timestamp}
/>
</React.Fragment>
);
})}
</MessageList>
<MessageBar
onSend={(payload) => sendMessage(payload, "UserA")}
/>
</Chat>Chat: Main wrapper that defines the chat layout.ChatHeader: Displays the chat title, status, and optional actions (e.g., clear/delete).MessageList: Scrollable container for messages.DateDivider: Separates messages by date.Message: Renders individual messages, aligned left/right based onisMe.MessageBar: Input area for sending text and files.
- Override styles using Tailwind classes or custom CSS.
- Extend functionality by passing callbacks (
onSend,onClick, etc.) to integrate with any backend. - File support is built in, drag‑and‑drop, paste, or select files directly in the message bar.
The development environment provides an interactive playground where you can explore, preview, and test all chat UI components in real time. It’s designed to make iteration easy, so you can quickly validate layout changes, props, and new features such as avatars, unread badges, and truncation behavior.
-
Clone the repository
git clone https://github.com/alejotoro-o/chat-ui.git
-
Navigate into the project folder
cd chat-ui -
Install dependencies
npm install
-
Run the development server
npm run dev
-
Open the environment in your browser: By default, the server runs at http://localhost:5173/. You’ll see the component gallery with live previews.
- Browse all available components side by side.
- Test components props.
- Validate responsive behavior (flex layouts, truncation, scrollable lists).
- Experiment with designs and Tailwind classes.
This package provides the following components:
The Chat component is the main wrapper for a chat interface. It defines the layout and contains the header, message list, and input bar.
type ChatProps = {
children: React.ReactNode
className?: string
}- children: React nodes to render inside the chat (header, list, bar).
- className: Optional Tailwind/custom classes to style the Chat container.
The ChatHeader component displays the chat title, avatar, status, and an optional action menu.
type ChatHeaderOption = {
label: string;
onClick: () => void;
destructive?: boolean; // optional flag for styling (e.g. red text for delete)
};
type ChatHeaderProps = {
name: string;
onClick?: () => void;
avatar?: boolean;
imageUrl?: string;
status?: string;
className?: string;
options?: ChatHeaderOption[]
};- name: Display name of the chat or contact.
- onClick: Optional callback to perform an action when the user’s profile is clicked.
- avatar: Enables/disables avatar rendering.
- If
trueandimageUrlis provided, the avatar image is shown. - If
trueandimageUrlis not provided, a fallback avatar is displayed. - If
false, no avatar is rendered.
- If
- imageUrl: Optional avatar image source URL.
- status: Presence text (e.g., “Online”, “Typing…”).
- className: Optional styles for the header container.
- options: Array of actions with
label,onClick, and optionaldestructiveflag.
The Message component renders a single message bubble with optional text, files, and timestamp. Alignment is controlled by the isMe flag.
type MessageProps = {
text?: string;
files?: { name: string; url: string; type: string }[];
isMe: boolean;
timestamp: Date;
status?: "sending" | "sent" | "delivered" | "read";
classNameMe?: string;
textColorMe?: string;
classNameOther?: string;
textColorOther?: string;
classNameRead?: string;
className?: string;
};- text: Message text content.
- files: Array of file attachments with preview URLs.
- isMe: Whether the message belongs to the current user.
- timestamp: Date object used for display and grouping.
- status: Current delivery state of the message.
"sending": Message is being sent."sent": Message successfully sent."delivered": Message has been received by the recipient."read": Recipient has opened the chat and seen the message.
- classNameMe / textColorMe: Styles for the “me” bubble and text.
- classNameOther / textColorOther: Styles for the “other” bubble and text.
- classNameRead: Styles applied when the message status is
"read". - className: Styles for the outer wrapper.
The MessageList component is a scrollable container for messages and date dividers.
type MessageListProps = {
children: React.ReactNode;
className?: string;
classNameScrollButton?: string;
};- children: Messages and related helpers (e.g.,
DateDivider). - className: Styles for the list container.
- classNameScrollButton: Styles for the scroll-to-bottom button.
The MessageBar component provides an input area for sending messages and files.
type MessageBarProps = {
onSend: (payload: { text?: string; files?: File[] }) => void;
placeholder?: string;
className?: string;
allowFiles?: boolean;
allowedFiles?: string;
maxFiles?: number;
maxFileSize?: number;
errorMessage?: {
invalidType?: string;
maxFiles?: string;
maxSize?: string;
};
classNameAttachIcon?: string;
classNameSendIcon?: string;
};- onSend: Callback invoked with text and/or files when the user sends.
- placeholder: Input placeholder text.
- className: Styles for the bar container.
- allowFiles: Enables file selection/drag‑and‑drop if true.
- allowedFiles: A comma‑separated string of accepted file types for validation. Supports MIME types (e.g.,
application/pdf), MIME globs (e.g.,image/*), or file extensions (e.g.,.jpg,.png). - maxFiles: Maximum number of files that can be attached in one message. If exceeded, the component will display an error.
- maxFileSize: Maximum size (in MB) allowed per file. Files larger than this will trigger an error.
- errorMessage: Object containing custom string messages for file validation failures:
- invalidType: Message shown when a file’s type or extension is not allowed.
- maxFiles: Message shown when the file limit is exceeded.
- maxSize: Message shown when a file exceeds the maximum size.
- classNameAttachIcon: Styles for the attachment icon.
- classNameSendIcon: Styles for the send icon.
The ChatItem component represents a row in a chat list, showing name, last message, date, avatar, and an options menu.
type ChatItemOptions = {
label: string;
onClick: () => void;
destructive?: boolean;
};
type ChatItemProps = {
id: string;
name: string;
lastMessage: string;
lastMessageStatus?: "sending" | "sent" | "delivered" | "read";
date: Date;
onClick: (id: string) => void;
avatar?: boolean,
imageUrl?: string;
unreadCount?: number;
options?: ChatItemOptions[];
classNameRead?: string;
classNameOptions?: string;
classNameUnreadCount?: string;
classNameUnreadDate?: string;
className?: string;
};- id: Unique identifier passed to
onClick. - name: Chat or contact name.
- lastMessage: Preview of the most recent message.
- status: Current delivery state of the last message sent.
"sending": Message is being sent."sent": Message successfully sent."delivered": Message has been received by the recipient."read": Recipient has opened the chat and seen the message.
- date: Date of the last activity.
- onClick: Handler invoked when the item is clicked.
- avatar: Enables/disables avatar rendering.
- If
trueandimageUrlis provided, the avatar image is shown. - If
trueandimageUrlis not provided, a fallback avatar is displayed. - If
false, no avatar is rendered.
- If
- imageUrl: Optional avatar image URL.
- unreadCount: Number of unread messages. If greater than zero, a badge is displayed.
- options: Array of actions for the overflow menu (e.g., clear chat, delete chat).
- classNameRead: Custom styles applied to the
readstatus in the last message sent. - classNameOptions: Custom styles applied to the options menu container and items.
- classNameUnreadCount: Custom styles applied to the unread badge (circle with count).
- classNameUnreadDate: Custom styles applied to the date element when there are unread messages.
- className: Custom styles for the list item wrapper.
The ChatList component is a container for multiple ChatItem entries.
type ChatListProps = {
children: React.ReactNode
className?: string
}- children: One or more
ChatItemelements. - className: Styles for the list container.
The DateDivider component separates messages by date with a label and horizontal lines.
type DateDividerProps = {
date: Date
locale?: string
className?: string
classNameLines?: string
classNameText?: string
}- date: Date object to display as the divider label.
- locale: Locale string used for date formatting.
- className: Styles for the divider container.
- classNameLines: Styles for the horizontal lines.
- classNameText: Styles for the center date label.
Here’s a polished Utilities section in Markdown, with a short explanation and prop descriptions for formatDate. Ready for copy‑paste into your README:
A helper function to format a Date object into a short, human‑readable string (day, month, year only). Useful for rendering timestamps in messages or dividers.
// Utility to format just the date (no time)
export const formatDate = (date: Date, locale: string = "en-US") =>
date.toLocaleDateString(locale, {
day: "2-digit",
month: "short",
year: "numeric",
});date: A JavaScriptDateobject to format.locale: Optional locale string (default"en-US"). Determines language and formatting style.
formatDate(new Date());
// "26 Nov 2025" (depending on locale)