components / conversation
Message
Chat message layout with user and assistant roles and hover actions.
previewmessage
You said:
How do I center a div?
Assistant said:
Use flexbox: put
display: flex with place-content: center on the parent.install
bash
npx shadcn@latest add https://turnui.xyz/r/message.jsonprops
| prop | type | default | description |
|---|---|---|---|
| role | "user" | "assistant" | "assistant" | Aligns and styles the message. User messages render as a bubble. |
| label | string | false | — | Screen-reader-only speaker label. Defaults to "You said:" or "Assistant said:". Pass false to omit. |
accessibility
- Each message is prefixed with a screen-reader-only speaker ("You said:" / "Assistant said:"). Put the list in a container with role="log" and aria-live="polite", and give it tabIndex={0} so keyboard users can scroll a log made only of text.
- Set aria-busy on that container while a reply streams so screen readers announce it once, not per token.
- Actions are revealed on hover and on keyboard focus.
source
tsx
import * as React from "react";
import { cn } from "@/lib/utils";
export type MessageRole = "user" | "assistant";
export function Message({
role = "assistant",
label,
className,
children,
...props
}: React.ComponentProps<"div"> & {
role?: MessageRole;
/** Spoken before the message so screen readers know the speaker. Pass false to omit. */
label?: string | false;
}) {
const speaker = label ?? (role === "user" ? "You said:" : "Assistant said:");
return (
<div
data-role={role}
className={cn(
"group flex w-full gap-3",
role === "user" && "justify-end",
className,
)}
{...props}
>
{speaker && <span className="sr-only">{speaker}</span>}
{children}
</div>
);
}
export function MessageContent({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
className={cn(
"min-w-0 text-[15px] leading-relaxed",
"group-data-[role=assistant]:max-w-[44rem] group-data-[role=assistant]:flex-1",
"group-data-[role=user]:max-w-[85%] group-data-[role=user]:rounded group-data-[role=user]:bg-muted group-data-[role=user]:px-4 group-data-[role=user]:py-2.5",
className,
)}
{...props}
/>
);
}
export function MessageActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
className={cn(
"mt-1 flex items-center gap-0.5 transition-opacity [@media(hover:hover)]:opacity-0 group-hover:opacity-100 focus-within:opacity-100",
className,
)}
{...props}
/>
);
}