Blode UI
GitHub

Input Message

A chat composer with an auto-resizing textarea, action slots, a send button, and drag-and-drop attachments.

Loading
Loading...
"use client";
 
import { useState } from "react";
 
import { InputMessage } from "@/components/ui/input-message";
 
export function InputMessageDemo() {
  const [value, setValue] = useState("");
 
  return (
    <div className="w-full max-w-md">
      <InputMessage onSend={() => setValue("")} onValueChange={setValue} value={value} />
    </div>
  );
}

Installation

npx shadcn@latest add "https://ui.blode.co/r/styles/default/input-message"

Usage

import { InputMessage } from "@/components/ui/input-message";
const [value, setValue] = useState("");
 
<InputMessage value={value} onValueChange={setValue} onSend={(text) => send(text)} />;

Examples

With attachments

Wire the leftSlot render function to openFilePicker, and control attachments with files / onFilesChange. Files can also be dropped directly onto the composer.

Loading
Loading...
"use client";
 
import { Paperclip2Icon } from "blode-icons-react";
import { useState } from "react";
 
import { Button } from "@/components/ui/button";
import { InputMessage } from "@/components/ui/input-message";
 
export function InputMessageWithAttachments() {
  const [value, setValue] = useState("");
  const [files, setFiles] = useState<File[]>([]);
 
  return (
    <div className="w-full max-w-md">
      <InputMessage
        files={files}
        // biome-ignore lint/correctness/noNestedComponentDefinitions: leftSlot is a render prop, not a component
        // eslint-disable-next-line react/no-unstable-nested-components -- leftSlot is a render prop, not a component
        leftSlot={({ openFilePicker }) => (
          <Button
            aria-label="Attach files"
            onClick={() => openFilePicker()}
            size="icon-sm"
            type="button"
            variant="ghost"
          >
            <Paperclip2Icon />
          </Button>
        )}
        onFilesChange={setFiles}
        onSend={() => {
          setValue("");
          setFiles([]);
        }}
        onValueChange={setValue}
        value={value}
      />
    </div>
  );
}

With slots

Both leftSlot and rightSlot accept arbitrary content rendered around the built-in send button.

Loading
Loading...
"use client";
 
import { GlobeIcon, MicrophoneIcon, Paperclip2Icon } from "blode-icons-react";
import { useState } from "react";
 
import { Button } from "@/components/ui/button";
import { InputMessage } from "@/components/ui/input-message";
 
export function InputMessageWithSlots() {
  const [value, setValue] = useState("");
 
  return (
    <div className="w-full max-w-md">
      <InputMessage
        leftSlot={
          <>
            <Button aria-label="Attach files" size="icon-sm" type="button" variant="ghost">
              <Paperclip2Icon />
            </Button>
            <Button aria-label="Search the web" size="icon-sm" type="button" variant="ghost">
              <GlobeIcon />
            </Button>
          </>
        }
        onSend={() => setValue("")}
        onValueChange={setValue}
        rightSlot={
          <Button aria-label="Dictate" size="icon-sm" type="button" variant="ghost">
            <MicrophoneIcon />
          </Button>
        }
        value={value}
      />
    </div>
  );
}