vllnt-ui
componentai-chat-inputsource: stale · stale
AI Chat Input
Prompt composer for conversational interfaces with helper text, toolbar actions, and submit states.
npx shadcn add "https://ui.vllnt.com/r/ai-chat-input.json"free
This source snapshot is older than our freshness window and couldn't be re-verified just now — it may be outdated.
Where it goes
statschat
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Files1 · source cached 10/8/2026, 4:29:08 AM
registry/default/ai-chat-input/ai-chat-input.tsx
import { cva } from "class-variance-authority";
import { LoaderCircle, SendHorizontal } from "lucide-react";
import { cn } from "@vllnt/ui";
import { Button } from "@vllnt/ui";
import { Textarea } from "@vllnt/ui";
const formShellVariants = cva(
"rounded-2xl border border-input bg-background shadow-sm",
);
function AIChatInputFooter({
currentLength,
helperText,
isSubmitDisabled,
isSubmitting,
maxLength,
status,
submitLabel,
}: {
currentLength: number;
helperText?: string;
isSubmitDisabled: boolean;
isSubmitting: boolean;
maxLength?: number;
status?: string;
submitLabel: string;
}) {
return (
<div className="flex flex-col gap-3 border-t border-border/60 pt-3 sm:flex-row sm:items-end sm:justify-between">
<div className="space-y-1 text-xs text-muted-foreground">
{helperText ? <p>{helperText}</p> : null}
<div className="flex flex-wrap items-center gap-2">
{status ? <span>{status}</span> : null}
{typeof maxLength === "number" ? (
<span>
{currentLength}/{maxLength}
</span>
) : null}
</div>
</div>
<Button
className="self-start rounded-full px-4 sm:self-auto"
disabled={isSubmitDisabled}
type="submit"
>
{isSubmitting ? (
<LoaderCircle
className="mr-2 size-4 animate-spin motion-reduce:[animation-duration:1.5s]"
data-motion="essential"
/>
) : (
<SendHorizontal className="mr-2 size-4" />
)}
{submitLabel}
</Button>
</div>
);
}
export type AIChatInputProps = React.ComponentPropsWithoutRef<"form"> & {
/** Disables editing and submit actions. */
disabled?: boolean;
/** Optional helper text shown below the prompt field. */
helperText?: string;
/**
* Accessible name for the message textarea. Defaults to `"Chat message"`.
* Override `textareaProps.aria-label` / `aria-labelledby` for finer control.
*/
inputLabel?: string;
/** Whether the submit action is in progress. */
isSubmitting?: boolean;
/** Called whenever the textarea value changes. */
onValueChange?: (value: string) => void;
/** Optional status text shown beside helper copy. */
status?: string;
/** Label for the submit button. */
submitLabel?: string;
/** Props forwarded to the textarea primitive. */
textareaProps?: React.TextareaHTMLAttributes<HTMLTextAreaElement>;
/** Optional controls rendered above the footer row. */
toolbar?: React.ReactNode;
/** Controlled textarea value. */
value?: string;
};
const AIChatInput = ({
className,
disabled = false,
helperText,
inputLabel = "Chat message",
isSubmitting = false,
onSubmit,
onValueChange,
ref,
status,
submitLabel = "Send",
textareaProps,
toolbar,
value,
...props
}: AIChatInputProps & { ref?: React.Ref<HTMLFormElement> }) => {
const currentValue = value ?? "";
const maxLength = textareaProps?.maxLength;
const isSubmitDisabled =
disabled || isSubmitting || currentValue.trim().length === 0;
return (
<form
className={cn(formShellVariants(), "w-full p-3", className)}
onSubmit={onSubmit}
ref={ref}
{...props}
>
<div className="space-y-3">
<Textarea
aria-label={inputLabel}
className="min-h-[120px] resize-none rounded-xl border-0 bg-transparent p-1 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
disabled={disabled}
onChange={(event) => {
textareaProps?.onChange?.(event);
onValueChange?.(event.target.value);
}}
placeholder="Ask a follow-up question, paste context, or describe what you need..."
value={value}
{...textareaProps}
/>
{toolbar ? <div className="flex flex-wrap gap-2">{toolbar}</div> : null}
<AIChatInputFooter
currentLength={currentValue.length}
helperText={helperText}
isSubmitDisabled={isSubmitDisabled}
isSubmitting={isSubmitting}
maxLength={maxLength}
status={status}
submitLabel={submitLabel}
/>
</div>
</form>
);
};
AIChatInput.displayName = "AIChatInput";
export { AIChatInput };