ChatInput
Prompt composer with controlled text, submit state, and trigger-menu support.
Composition
The preferred shape for composing the installed agent from its exported parts.
Parts
Exported compound parts from the installed source.
ChatInput├── ChatInputShell├── ChatInputAccent├── ChatInputTextarea├── ChatInputToolbar├── ChatInputTools├── ChatInputFooter└── ChatInputSubmitThis agent installs from the chat registry. Install the bundle with the command above, or inspect the source below.
npx shadcn@latest add http://127.0.0.1:3000/r/refined/chat.jsonUsage
import { ChatInput, ChatInputShell, ChatInputSubmit, ChatInputTextarea, ChatInputToolbar, ChatInputTools,} from "@/components/refined-ui/chat-input";type MastraTextMessageInput = { role: "user"; content: { format: 2; parts: [{ type: "text"; text: string }]; };};function createMastraTextMessage(text: string): MastraTextMessageInput { return { role: "user", content: { format: 2, parts: [{ type: "text", text }], }, };}export function Example({ send }: { send: (message: MastraTextMessageInput) => void | Promise<void> }) { return ( <ChatInput onSubmit={async ({ value, clear }) => { await send(createMastraTextMessage(value)); clear(); }} > <ChatInputShell> <ChatInputTextarea placeholder="Ask anything..." /> <ChatInputToolbar> <ChatInputTools>Mastra</ChatInputTools> <ChatInputSubmit>Send</ChatInputSubmit> </ChatInputToolbar> </ChatInputShell> </ChatInput> );}Installed dependencies
Installed with this agent because the visible component depends on them; they are support files, not separate product surfaces.
src/registry/hooks/use-chat-input.tsHooksrc/registry/sources/refined/chat-input-editor/schema.tsSupportsrc/registry/sources/refined/chat-input-editor/types.tsSupportsrc/registry/sources/refined/chat-input-editor/ghost.tsSupportsrc/registry/sources/refined/chat-input-editor.cssSupportRaw code
Primary installed agent source
"use client";import type { ChangeEvent, ComponentProps } from "react";import { createContext, useContext } from "react";import type { ChatInputProps } from "@/components/refined-ui/contracts";import { useChatInput } from "@/components/refined-ui/hooks/use-chat-input";import { cn } from "@/components/refined-ui/lib/cn";import { hasSkinAdornment, skinAdornment, skinSlot } from "@/components/refined-ui/skin";import { Button } from "@/components/refined-ui/ui/button";type ChatInputContextValue = ReturnType<typeof useChatInput>;const ChatInputContext = createContext<ChatInputContextValue | null>(null);// Exported so opt-in editor extras (chat-input-editor.tsx) share this context w/o pulling ProseMirror deps into base file.export function useChatInputContext() { const context = useContext(ChatInputContext); if (!context) throw new Error("ChatInput compound components must be rendered inside <ChatInput>."); return context;}export function ChatInput({ value, defaultValue, onValueChange, onSubmit, state = "idle", density = "comfortable", disabled = false, className, children, ...props}: ChatInputProps) { const input = useChatInput({ value, defaultValue, onValueChange, onSubmit, state, density, disabled, trackSends: hasSkinAdornment("chat-input:send-layer"), }); const sendLayer = skinAdornment("chat-input:send-layer", { sendCount: input.sendCount }); return ( <ChatInputContext.Provider value={input}> <form data-ui="agent" data-component="chat-input" data-state={state} data-density={density} onSubmit={input.handleSubmit} className={cn( // sticky already makes the form the containing block + stacking context the send-layer anchor relies on "sticky bottom-0 w-full bg-background/80 px-2 pb-2 pt-1 backdrop-blur supports-[backdrop-filter]:bg-background/70", skinSlot("chat-input", {}), className, )} {...props} > {/* send-layer anchor: the component owns this position contract (backdrop behind the shell, hit-test/a11y/paint contained); the skin only supplies visuals. Zero DOM when no skin fills the anchor. */} {sendLayer !== undefined && sendLayer !== null ? ( <div aria-hidden="true" className="pointer-events-none absolute inset-0 -z-10 overflow-clip [contain:paint]"> {sendLayer} </div> ) : null} {children} </form> </ChatInputContext.Provider> );}export type ChatInputShellProps = ComponentProps<"div">;export function ChatInputShell({ className, ...props }: ChatInputShellProps) { const input = useChatInputContext(); return ( <div data-ui="agent" data-slot="chat-input-shell" data-state={input.state} className={cn( "relative overflow-hidden rounded-field border bg-card/78 shadow-sm ring-1 ring-foreground/4 transition-[box-shadow,filter,translate] duration-[var(--duration-base)] ease-[var(--ease-emphasized)]", "data-[state=submitting]:-translate-y-0.5 data-[state=submitting]:saturate-[1.02] data-[state=submitting]:shadow-md", skinSlot("chat-input-shell", {}), className, )} {...props} /> );}export type ChatInputAccentProps = ComponentProps<"div">;export function ChatInputAccent({ className, ...props }: ChatInputAccentProps) { return ( <div aria-hidden="true" className={cn( "pointer-events-none absolute inset-x-5 top-0 h-px bg-gradient-to-r from-transparent via-foreground/20 to-transparent", skinSlot("chat-input-accent", {}), className, )} {...props} /> );}export type ChatInputTextareaProps = ComponentProps<"textarea">;export function ChatInputTextarea({ className, rows, disabled, onChange, ...props }: ChatInputTextareaProps) { const input = useChatInputContext(); function handleChange(event: ChangeEvent<HTMLTextAreaElement>) { onChange?.(event); if (!event.defaultPrevented) input.setValue(event.currentTarget.value); } return ( <textarea {...props} data-ui="agent" data-component="chat-input-textarea" aria-label="Message" value={input.value} onChange={handleChange} disabled={disabled ?? input.isDisabled} rows={rows ?? input.rows} className={cn( // CSS-first auto-grow via field-sizing-content (bounded min/max-h, no ref/ResizeObserver/scrollHeight); rows = fallback where unsupported. "field-sizing-content min-h-16 max-h-[40dvh] w-full resize-none bg-transparent px-[var(--padding-x)] py-[var(--padding-y)] text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60", skinSlot("chat-input-textarea", {}), className, )} /> );}export type ChatInputToolbarProps = ComponentProps<"div">;export function ChatInputToolbar({ className, ...props }: ChatInputToolbarProps) { return ( <div className={cn("flex min-h-10 items-center justify-between gap-2 px-2.5 pb-2", skinSlot("chat-input-toolbar", {}), className)} {...props} /> );}export type ChatInputToolsProps = ComponentProps<"div">;export function ChatInputTools({ className, ...props }: ChatInputToolsProps) { return ( <div className={cn("flex min-w-0 items-center gap-1.5 text-muted-foreground", skinSlot("chat-input-tools", {}), className)} {...props} /> );}export type ChatInputFooterProps = ComponentProps<"div">;export function ChatInputFooter({ className, ...props }: ChatInputFooterProps) { return <div className={cn("px-3 pb-2 text-meta text-muted-foreground", skinSlot("chat-input-footer", {}), className)} {...props} />;}export type ChatInputSubmitProps = ComponentProps<typeof Button>;export function ChatInputSubmit({ className, disabled, children = "Send", ...props }: ChatInputSubmitProps) { const input = useChatInputContext(); return ( <Button data-ui="agent" data-component="chat-input-submit" type="submit" variant="solid" size="xs" disabled={disabled ?? !input.canSubmit} className={cn( "h-7", input.canSubmit ? "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90" : "cursor-not-allowed bg-muted text-muted-foreground", skinSlot("chat-input-submit", {}), className, )} {...props} > {children} </Button> );}Available extensions
Optional, separately installed items this surface can host — not part of the component's bundle.
Anchored ChatInput extension: a blurred aurora backdrop that sweeps up once per sent message — activated from skin.config via the chat-input:send-layer anchor.
npx shadcn@latest add http://127.0.0.1:3000/r/refined/send-aurora.json// skin.config.tsx (import the "use client" extension; the config itself stays RSC-pure)import { SendAurora } from "@/components/refined-ui/extensions/send-aurora";export const skin: RefinedSkin = { id: "my-brand", adornments: { "chat-input:send-layer": (ctx) => <SendAurora sendCount={ctx.sendCount} />, },};Primitives
This agent composes these library primitives, installed under components/refined-ui/ui/*.