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 { InputBar } from "@/components/ui/input-bar";
function ChevronDown() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-60"
>
<polyline points="6 9 12 15 18 9" />
</svg>
);
}
function CursorIcon() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z" />
</svg>
);
}
function PillButton({
children,
ariaLabel,
}: {
children: React.ReactNode;
ariaLabel: string;
}) {
return (
<button
type="button"
aria-label={ariaLabel}
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
>
{children}
</button>
);
}
export default function ToolbarActionsDemo() {
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"
leftActions={
<>
<PillButton ariaLabel="Select mode">
<CursorIcon />
<span>Agent</span>
<ChevronDown />
</PillButton>
<PillButton ariaLabel="Select model">
<span>Sonnet</span>
<span className="text-neutral-400 dark:text-neutral-500">
4.6
</span>
<ChevronDown />
</PillButton>
</>
}
/>
</div>
</div>
);
}