Skip to main content
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}
        />
      );
    }
    

    With Context Menu

    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).
    onChange
    (value: string) => void
    Callback fired when the input value changes.
    disabled
    boolean
    default:"false"
    Whether the composer is disabled.
    showToolsButton
    boolean
    default:"true"
    Whether to show the tools button in the toolbar.
    onToolSelect
    (tool: Tool) => void
    Callback fired when a tool is selected from the slash command dropdown.
    tools
    Tool[]
    default:"[]"
    Array of available tools for the slash command dropdown.
    onAttachClick
    () => void
    Callback fired when the attach button is clicked (if no context options are provided).
    contextOptions
    ComposerContextOption[]
    Context menu options shown in the plus button dropdown.
    autoFocus
    boolean
    default:"false"
    Whether to auto-focus the input on mount.
    maxRows
    number
    default:"8"
    Maximum number of rows for the auto-growing textarea.
    defaultValue
    string
    default:"''"
    Initial value for uncontrolled usage.
    value
    string
    Controlled value of the input.
    className
    string
    Additional CSS classes for the container.
    attachedFiles
    UploadedFile[]
    default:"[]"
    Array of files to display in the preview area.
    onRemoveFile
    (id: string) => void
    Callback fired when a file is removed from the preview.
    isLoading
    boolean
    default:"false"
    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

    Build docs developers (and LLMs) love