Every component is a small family of parts that share context. You compose the parts you need and skip the rest.
Compose, don’t configure
A prompt input is a container, a textarea, and an actions row. Icons and buttons are yours:
import { ArrowUpIcon, PaperClipIcon } from "@heroicons-animated/react";
import { Button } from "@/components/ui/button";
import {
PromptInput,
PromptInputAction,
PromptInputActionGroup,
PromptInputActions,
PromptInputTextarea,
} from "@/components/pandacoderz-ui/prompt-input";
export function ChatInput({ onSend }: { onSend: (text: string) => void }) {
return (
<PromptInput onSubmit={onSend}>
<PromptInputTextarea />
<PromptInputActions>
<PromptInputActionGroup>
<PromptInputAction asChild tooltip="Attach">
<Button variant="ghost" size="icon-sm" className="rounded-full">
<PaperClipIcon size={16} className="flex" />
</Button>
</PromptInputAction>
</PromptInputActionGroup>
<PromptInputActionGroup>
<PromptInputAction asChild tooltip={{ content: "Send", shortcut: "↵" }}>
<Button size="icon-sm" className="rounded-full">
<ArrowUpIcon size={16} className="flex" />
</Button>
</PromptInputAction>
</PromptInputActionGroup>
</PromptInputActions>
</PromptInput>
);
}asChild
Parts that wrap interactive elements accept asChild. The part then merges its behavior (tooltip, data attributes, click handling) into your element instead of rendering an extra wrapper.
Icons
The library uses Heroicons Animated. Each icon renders a div wrapper around an SVG and takes a numeric size, so pass size={16} and className="flex" for inline alignment. Swap in any icon set you like; nothing depends on the specific package beyond the defaults inside thread, reasoning, tool, and model-selector.
Streaming
Components that render text (MessageMarkdown, ReasoningContent) accept partial markdown and re-render as it grows. Wrap the conversation in Thread so the viewport follows new content, and drive state with a functional update:
for await (const chunk of stream) {
setText((prev) => prev + chunk);
}See the AI Chat block for a complete reducer over reasoning, tool, and text events.
Styling
Override any part with className. Every part sets a data-slot attribute, so you can also target them from a parent:
[data-slot="message-content"] {
max-width: 65ch;
}