Skip to content
EMBOSS
Docs menu

Chat Message

A conversation surface with physical turn-taking — user messages are placed onto the surface, assistant messages are milled into it.

@emboss/chat-message
Align the tape heads.
Checking alignment…

Installation

pnpm dlx shadcn@latest add @emboss/chat-message

Usage

example.tsx
import { ChatLog, ChatMessage } from "@/components/ui/chat-message";

export function Example() {
  return (
    <ChatLog aria-label="Conversation">
      <ChatMessage from="user">Align the heads.</ChatMessage>
      <ChatMessage from="assistant">
        Aligning. Azimuth is within tolerance.
      </ChatMessage>
    </ChatLog>
  );
}

API reference

ChatLog

The conversation container: role=log makes appended messages announce politely. Label it with aria-label.

ChatMessage

PropTypeDefaultDescription
from"user" | "assistant" | "system""assistant"Who is speaking; sets alignment and depth (placed plate, milled output, or flush annotation).
Data attributeDescription
data-fromReflects the speaker for styling.

Source

View source — chat-message.tsx
chat-message.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

/**
 * A conversation surface with physical turn-taking: user messages are
 * plates placed onto the surface, assistant messages are milled into it.
 * The log container is a polite live region, so appended messages announce
 * themselves without per-token noise.
 *
 * @example
 * <ChatLog aria-label="Conversation">
 *   <ChatMessage role="user">Align the heads.</ChatMessage>
 *   <ChatMessage role="assistant">Aligning. Azimuth is within tolerance.</ChatMessage>
 * </ChatLog>
 */
function ChatLog({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="chat-log"
      role="log"
      className={cn("flex w-full flex-col gap-3", className)}
      {...props}
    />
  );
}

const chatMessageVariants = cva(
  "flex max-w-[85%] flex-col gap-1 rounded-lg px-4 py-2.5 text-sm leading-relaxed",
  {
    variants: {
      from: {
        /** A plate you placed onto the surface. */
        user: "self-end bg-surface-2 text-ink shadow-emboss-1",
        /** The machine's engraved output, milled into the surface. */
        assistant: "self-start bg-well text-ink shadow-deboss-1",
        /** Quiet system annotations, flush with the page. */
        system:
          "tracking-label max-w-full justify-center self-center bg-surface-1 font-mono text-label text-ink-faint uppercase shadow-flush",
      },
    },
    defaultVariants: {
      from: "assistant",
    },
  },
);

export type ChatMessageProps = React.ComponentProps<"div"> &
  VariantProps<typeof chatMessageVariants>;

function ChatMessage({ className, from, ...props }: ChatMessageProps) {
  return (
    <div
      data-slot="chat-message"
      data-from={from ?? "assistant"}
      className={cn(chatMessageVariants({ from }), className)}
      {...props}
    />
  );
}

export { ChatLog, ChatMessage };