Components
Chat Input
A chat composer with pasted-screenshot thumbnails, a capability-toggle slot and an icon-only send button — mobile-ready out of the box.
Installation
$npx litefy@latest add chat-input
$pnpm dlx litefy@latest add chat-input
$yarn dlx litefy@latest add chat-input
$bun --bun litefy@latest add chat-input
Usage
ChatInput manages the text and pasted-image state for you: screenshots pasted into the textarea become a thumbnail strip (URL.createObjectURL handled internally), and the send button emits the collected payload through onSend, clearing the composer afterwards.
Capability toggles go into the actions slot — compose them from Toggle yourself.
"use client";import * as React from "react";import { Brain, Globe } from "lucide-react";import { ChatInput, Toggle } from "@/ui";export default function Demo() { const [deepThink, setDeepThink] = React.useState(false); const [webSearch, setWebSearch] = React.useState(false); return ( <ChatInput classNames={{ container: "w-96 max-w-full" }} placeholder="Type a message, try pasting a screenshot…" attach onSend={({ text, images }) => { console.log("send:", text, images, { deepThink, webSearch }); }} actions={ <> <Toggle checked={deepThink} onCheckedChange={setDeepThink} className="px-2.5 py-1 text-xs"> <Brain /> Deep think </Toggle> <Toggle checked={webSearch} onCheckedChange={setWebSearch} className="px-2.5 py-1 text-xs"> <Globe /> Web search </Toggle> </> } /> );}Mobile adaptation
- Enter behavior: on fine pointers (mouse) Enter sends and Shift+Enter inserts a newline; on coarse pointers (touch) Enter always inserts a newline — the send button is the send affordance, so virtual keyboards never cause accidental sends. Control with
enterToSend: "auto" | boolean. - No focus zoom: the textarea renders at 16px on small screens (iOS Safari zooms focusable inputs below that size).
- Attach button: set
attachto show an image-picker button (accept="image/*"), which on mobile surfaces the camera/gallery chooser.
API Reference
ChatInput
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Controlled text |
defaultValue | string | "" | Initial text (uncontrolled) |
onValueChange | (text: string) => void | - | Text change callback |
onSend | (payload: { text: string; images: File[] }) => void | - | Send callback; the input clears itself afterwards |
placeholder | string | - | Textarea placeholder |
disabled | boolean | false | Disable the composer |
autoPaste | boolean | true | Turn pasted images into thumbnails |
attach | boolean | false | Show the image attach button |
enterToSend | "auto" | boolean | "auto" | Enter-to-send behavior |
actions | React.ReactNode | - | Left side of the action bar |
className | ClassNameValue | - | Custom classes, applied to the container |
classNames | container / textarea / images / actions / send | - | Custom classes per part |
styles | { container?, textarea?, actions?, send? } | - | Inline styles per part |