Components
Chat composer with auto-resizing textarea, attachment chips for staged images/files, send/stop button, and slot-based left/right toolbar actions for mode pickers, model selectors, etc. Self-contained port from 21st.dev Agent Elements.
npx shadcn@latest add https://21st.dev/r/21stdev/input-barLoading preview...
import { useState } from "react";
import { InputBar, type AttachedImage, type AttachedFile } from "@/components/ui/input-bar";
const PLACEHOLDER_IMG =
"data:image/svg+xml;utf8," +
encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='128' height='128' viewBox='0 0 128 128'>
<defs>
<linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>
<stop offset='0%' stop-color='#a78bfa'/>
<stop offset='100%' stop-color='#3b82f6'/>
</linearGradient>
</defs>
<rect width='128' height='128' fill='url(#g)'/>
<circle cx='44' cy='52' r='14' fill='#fff' opacity='0.85'/>
<path d='M16 110 L52 70 L80 96 L112 60 L112 120 L16 120 Z' fill='#fff' opacity='0.9'/>
</svg>`,
);
export default function WithAttachmentsDemo() {
const [images, setImages] = useState<AttachedImage[]>([
{ id: "img-1", filename: "hero.png", url: PLACEHOLDER_IMG, size: 482_133 },
]);
const [files, setFiles] = useState<AttachedFile[]>([
{ id: "file-1", filename: "spec.pdf", size: 1_240_500 },
]);
return (
<div className="min-h-screen w-full flex items-center justify-center p-6 bg-white dark:bg-neutral-950">
<div className="w-full max-w-md">
<InputBar
onSend={() => {}}
onStop={() => {}}
status="ready"
onAttach={() => {}}
attachedImages={images}
attachedFiles={files}
onRemoveImage={(id) =>
setImages((prev) => prev.filter((i) => i.id !== id))
}
onRemoveFile={(id) =>
setFiles((prev) => prev.filter((f) => f.id !== id))
}
/>
</div>
</div>
);
}