Components
- Accordion
- Alert
- Alert Dialog
- Ask User Questions
- Aspect Ratio
- Autocomplete
- Avatar
- Badge
- Bar List
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chat Message
- Checkbox
- Checkbox Group
- Circular Progress
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Button
- Currency Input
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input Message
- Input OTP
- Item
- Kbd
- Label
- Menubar
- Meter
- Multi Combobox
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Phone Input
- Popover
- Progress
- Progress List
- Prompt
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Thinking Indicator
- Thinking Steps
- Toggle
- Toggle Group
- Tooltip
- Typography
Loading...
import {
ArrowRotateCounterClockwiseIcon,
PencilIcon,
SquareBehindSquare1Icon,
} from "blode-icons-react";
import { Button } from "@/components/ui/button";
import { ChatMessage } from "@/components/ui/chat-message";
// Icon-only action buttons for the hover-revealed meta row. Assistant replies
// get copy + regenerate; user messages get copy + edit. Illustrative only —
// the buttons carry no behaviour in this demo.
function MessageActions({ from }: { from: "user" | "assistant" }) {
return (
<>
<Button aria-label="Copy message" size="icon-xs" variant="ghost">
<SquareBehindSquare1Icon />
</Button>
{from === "user" ? (
<Button aria-label="Edit message" size="icon-xs" variant="ghost">
<PencilIcon />
</Button>
) : (
<Button aria-label="Regenerate response" size="icon-xs" variant="ghost">
<ArrowRotateCounterClockwiseIcon />
</Button>
)}
</>
);
}
export function ChatMessageDemo() {
return (
<div className="flex w-full max-w-xl flex-col gap-2">
<ChatMessage actions={<MessageActions from="user" />} from="user" time="Wednesday 6:06 PM">
What does “good design” actually mean? Everyone says it, no one defines it.
</ChatMessage>
<ChatMessage actions={<MessageActions from="assistant" />} from="assistant">
Good design is mostly invisible — you only notice it when it's missing. It's less
about how something looks and more about how effortlessly it lets you do what you came to
do.
</ChatMessage>
<ChatMessage actions={<MessageActions from="user" />} from="user" time="Wednesday 6:07 PM">
So function over form?
</ChatMessage>
<ChatMessage actions={<MessageActions from="assistant" />} from="assistant">
Not quite. Form is part of function — something that feels good to use is, in a real sense,
working better. The split between the two is mostly a myth.
</ChatMessage>
<ChatMessage actions={<MessageActions from="user" />} from="user" time="Wednesday 6:08 PM">
That reframes it completely.
</ChatMessage>
</div>
);
}Installation
npx shadcn@latest add "https://ui.blode.co/r/styles/default/chat-message"
Usage
import { ChatMessage } from "@/components/ui/chat-message";<ChatMessage from="user" time="Wednesday 6:08 PM">
Can you summarize the Q3 report?
</ChatMessage>
<ChatMessage from="assistant">Revenue grew 14% quarter-over-quarter.</ChatMessage>Examples
With attachments
Pass File objects to files to render attachment thumbnails above the message. Images show a
preview; other files show an icon and name.
Loading...
import { ChatMessage } from "@/components/ui/chat-message";
export function ChatMessageWithAttachments() {
const files = [
new File(["%PDF-1.4"], "design-brief.pdf", { type: "application/pdf" }),
new File(["notes"], "research-notes.txt", { type: "text/plain" }),
];
return (
<div className="flex w-full max-w-md flex-col gap-3">
<ChatMessage files={files} from="user">
Here are the files for review.
</ChatMessage>
<ChatMessage from="assistant">
Got them — I'll go through the brief and pull out the open questions.
</ChatMessage>
</div>
);
}