The Composer component is a full-featured chat input interface that combines message composition with file attachments, slash command tool selection, and context menu options. It features an auto-growing textarea, smart keyboard handling, and seamless integration with other Gaia UI components.
Preview
Features
- Auto-growing textarea - Expands as you type, up to a configurable maximum
- File attachments - Built-in file preview with drag-and-drop support
- Slash commands - Integrated tool selection dropdown
- Context menu - Customizable plus button with actions
- Keyboard shortcuts - Enter to send, Shift+Enter for new line, Escape to close menus
- Loading states - Disabled state when processing
- Light/dark mode - Fully themed for both modes
Installation
Usage
Basic Example
import { Composer } from "@/components/ui/composer";
export function ChatInput() {
const handleSubmit = (message: string, files?: UploadedFile[]) => {
console.log("Message:", message);
console.log("Files:", files);
};
return (
<Composer
placeholder="What can I do for you today?"
onSubmit={handleSubmit}
/>
);
}
With Tools Integration
import { Composer, type Tool } from "@/components/ui/composer";
import { useState } from "react";
const tools: Tool[] = [
{
name: "search",
category: "search",
description: "Search the web for information"
},
{
name: "create_image",
category: "creative",
description: "Generate AI images"
}
];
export function ChatWithTools() {
const [value, setValue] = useState("");
const handleToolSelect = (tool: Tool) => {
console.log("Tool selected:", tool);
// Optionally insert tool reference into message
setValue((prev) => `${prev}/${tool.name} `);
};
return (
<Composer
value={value}
onChange={setValue}
tools={tools}
onToolSelect={handleToolSelect}
showToolsButton={true}
/>
);
}
With File Attachments
import { Composer, type UploadedFile } from "@/components/ui/composer";
import { useState } from "react";
export function ChatWithFiles() {
const [files, setFiles] = useState<UploadedFile[]>([]);
const handleAttachClick = () => {
// Trigger file picker
const input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.onchange = (e) => {
const target = e.target as HTMLInputElement;
if (target.files) {
const newFiles = Array.from(target.files).map((file) => ({
id: crypto.randomUUID(),
url: URL.createObjectURL(file),
name: file.name,
type: file.type,
}));
setFiles((prev) => [...prev, ...newFiles]);
}
};
input.click();
};
const handleRemoveFile = (id: string) => {
setFiles((prev) => prev.filter((f) => f.id !== id));
};
return (
<Composer
attachedFiles={files}
onRemoveFile={handleRemoveFile}
onAttachClick={handleAttachClick}
/>
);
}
import { Composer, type ComposerContextOption } from "@/components/ui/composer";
import { File01Icon, Image01Icon } from "@/components/icons";
const contextOptions: ComposerContextOption[] = [
{
id: "attach-file",
label: "Attach File",
icon: <File01Icon size={16} />,
description: "Upload a document",
onClick: () => console.log("Attach file")
},
{
id: "attach-image",
label: "Attach Image",
icon: <Image01Icon size={16} />,
description: "Upload an image",
onClick: () => console.log("Attach image")
}
];
export function ChatWithContext() {
return (
<Composer
contextOptions={contextOptions}
/>
);
}
Controlled Component
import { Composer } from "@/components/ui/composer";
import { useState } from "react";
export function ControlledComposer() {
const [message, setMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (msg: string) => {
setIsLoading(true);
// Send message to API
await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ message: msg })
});
setMessage(""); // Clear after sending
setIsLoading(false);
};
return (
<Composer
value={message}
onChange={setMessage}
onSubmit={handleSubmit}
isLoading={isLoading}
/>
);
}
Props
placeholder
string
default:"'What can I do for you today?'"
Placeholder text shown when the input is empty.
onSubmit
(message: string, files?: UploadedFile[]) => void
Callback fired when the user submits a message (Enter key or send button).
Callback fired when the input value changes.
Whether the composer is disabled.
Whether to show the tools button in the toolbar.
Callback fired when a tool is selected from the slash command dropdown.
Array of available tools for the slash command dropdown.
Callback fired when the attach button is clicked (if no context options are provided).
Context menu options shown in the plus button dropdown.
Whether to auto-focus the input on mount.
Maximum number of rows for the auto-growing textarea.
Initial value for uncontrolled usage.
Controlled value of the input.
Additional CSS classes for the container.
attachedFiles
UploadedFile[]
default:"[]"
Array of files to display in the preview area.
Callback fired when a file is removed from the preview.
Whether the composer is in a loading state (disables interactions).
Type Definitions
Tool
interface Tool {
/** Unique tool identifier */
name: string;
/** Category for grouping tools */
category: string;
/** Description shown below tool name */
description?: string;
/** Custom icon (defaults to category icon) */
icon?: React.ReactNode;
}
UploadedFile
interface UploadedFile {
id: string;
url: string;
name: string;
type: string;
description?: string;
isUploading?: boolean;
}
ComposerContextOption
interface ComposerContextOption {
id: string;
label: string;
icon?: ReactNode;
description?: string;
onClick?: () => void;
}
Accessibility
- Full keyboard navigation support
- Enter submits message, Shift+Enter adds new line
- Escape closes dropdowns
- Proper ARIA labels on interactive elements
- Focus indicators for keyboard users
Design Notes
- Uses Gaia UI’s primary color (#00bbff) for the send button
- Auto-growing textarea provides natural typing experience
- Generous touch targets (44x44px minimum) for mobile
- Smooth transitions and animations
- Respects user’s color scheme preference
Related Components