crucıble

Sluice

Prompt composer for chat UIs — an auto-growing textarea in a glass capsule with spring-in attachment chips, an optional model pill, and a send disc that morphs into a stop button while a light laps the border during generation. Enter/Shift+Enter/Escape, IME-safe, file paste, and labelled controls.

motionfree

Installation

CLI

npx shadcn@latest add @crucible/sluice

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
valuestringControlled draft text. Pair with onValueChange.
defaultValuestring""Initial draft text when uncontrolled.
onValueChange(value: string) => voidFires with the next draft on every edit (and with "" after submit), controlled or not.
onSubmit(text: string, attachments: SluiceAttachment[]) => voidFires on Enter or the send button with the draft and the current attachments. The composer clears its own draft (and, when uncontrolled, its attachments) right after.
busybooleanfalseThe assistant is working. The send button morphs into a stop button, a light traces the capsule border, and Enter no longer submits.
onStop() => voidFires from the stop button or Escape while busy.
placeholderstring"Ask anything…"Textarea placeholder.
attachmentsSluiceAttachment[]Controlled attachment chips. Omit to let the composer track files picked or pasted itself (cleared on submit).
onAttach(files: File[]) => voidFires with files chosen from the attach button or pasted from the clipboard.
onRemoveAttachment(id: string) => voidFires when a chip's remove button is pressed.
hideAttachbooleanfalseHide the attach button entirely.
modelsstring[]Model names for the compact model pill. The pill is hidden when omitted or empty.
modelstringControlled selected model. Pair with onModelChange.
defaultModelstringmodels[0]Initial model when uncontrolled.
onModelChange(model: string) => voidFires when the model pill changes, controlled or not.
maxRowsnumber8Rows the textarea grows to before it scrolls.
accentColorstring"#a78bfa" (ion violet)Accent for the busy border trace, stop button, and focus rings.
labelstring"Message"Accessible name for the textarea.
compactbooleanfalseEmbedded variant for panels and side sheets: a tighter, flatter field — smaller radius, quiet fill, no glass blur or drop shadow, trimmed padding.
Also accepts all props of Omit< React.ComponentPropsWithoutRef<"div">, "onSubmit" | "defaultValue" | "onChange" > — they pass through to the underlying element.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.