Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Reference
  2. useChatInput
Hooks

useChatInput

Controlled composer text and submit state.

Installed to components/refined-ui/hooks/use-chat-input.ts with the ChatInput component. It is local UI behavior — yours to own and edit.

Used by ChatInput

Source

Installed hook source

Behavior hook
src/registry/hooks/use-chat-input.ts
import type { FormEvent } from "react";
import { useState } from "react";
​
import type { ChatInputProps, ChatInputSubmitPayload } from "../contracts";
​
function useControllableText({
value,
defaultValue = "",
onValueChange,
}: Pick<ChatInputProps, "value" | "defaultValue" | "onValueChange">) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = value !== undefined;
const currentValue = isControlled ? value : internalValue;
​
function setValue(nextValue: string) {
if (!isControlled) setInternalValue(nextValue);
onValueChange?.(nextValue);
}
​
return [currentValue, setValue] as const;
}
​
export function useChatInput({
value,
defaultValue,
onValueChange,
onSubmit,
state = "idle",
density = "comfortable",
disabled = false,
trackSends = false,
}: Pick<ChatInputProps, "value" | "defaultValue" | "onValueChange" | "onSubmit" | "state" | "density" | "disabled"> & {
/** Count successful submits — only enabled when something reads the counter (the send-layer anchor), so idle apps pay no extra state update. */
trackSends?: boolean;
}) {
const [inputValue, setInputValue] = useControllableText({ value, defaultValue, onValueChange });
const [sendCount, setSendCount] = useState(0);
const normalizedValue = inputValue.trim();
const isDisabled = disabled || state === "disabled" || state === "submitting";
const canSubmit = normalizedValue.length > 0 && !isDisabled;
const isCompact = density === "compact";
​
function clear() {
setInputValue("");
}
​
// shared path: plain textarea via handleSubmit, rich editor calls submit() directly with extras (mentions)
function submit(extra?: Partial<ChatInputSubmitPayload>) {
if (!canSubmit) return;
if (trackSends) setSendCount((count) => count + 1);
void onSubmit?.({ value: normalizedValue, clear, ...extra });
}
​
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
submit();
}
​
return {
value: inputValue,
setValue: setInputValue,
normalizedValue,
state,
density,
isCompact,
isDisabled,
canSubmit,
rows: isCompact ? 2 : 4,
sendCount,
clear,
submit,
handleSubmit,
};
}
​

On this page

  • Installation
  • Source