Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Integration
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Agents
  • Action Bar
  • Audio Recorder
  • Audio VisualizerBetaBeta — the props contract is close to final, but small breaking changes can still land.
  • Chat Input
  • Chat Input Attachment
  • Chat Message
  • Chat Scene
  • Code Block Editor
  • Dynamic NotificationExpExperimental — the contract and the rendering can change without notice. Own the installed copy before shipping it.
  • Environment Variables
  • Inline Attachment
  • Markdown Block
  • Task List
  • Thread Rail
  • Tool Call
  • User Ask
Blocks
  • Chat
  • Theme toggle
  1. Agents
  2. ChatMessage
Agents

ChatMessage

Composable chat message with typed role, density, and tone state.

Composition

The preferred shape for composing the installed agent from its exported parts.

Parts

Exported compound parts from the installed source.

ChatMessage
├── ChatMessageRow
├── ChatMessageAvatar
├── ChatMessageBody
├── ChatMessageHeader
├── ChatMessageContent
└── ChatMessageActions

This agent installs from the chat registry. Install the bundle with the command above, or inspect the source below.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/chat.json
See registry manifest

Usage

import type { MastraDBMessage } from "@mastra/core/agent/message-list";
import { MessageFactory } from "@mastra/react";
import type { ReactNode } from "react";
import { ChatMessage, ChatMessageBody, ChatMessageContent, ChatMessageRow } from "@/components/refined-ui/chat-message";
import type { ChatRole } from "@/components/refined-ui/contracts";
​
function renderChatMessage(from: ChatRole, children: ReactNode) {
return (
<ChatMessage from={from}>
<ChatMessageRow>
<ChatMessageBody>
<ChatMessageContent>{children}</ChatMessageContent>
</ChatMessageBody>
</ChatMessageRow>
</ChatMessage>
);
}
​
export function Example({ message }: { message: MastraDBMessage }) {
return (
<MessageFactory
message={message}
roles={{
User: ({ children }) => renderChatMessage("user", children),
Assistant: ({ children }) => renderChatMessage("assistant", children),
System: ({ children }) => renderChatMessage("system", children),
Signal: ({ children }) => renderChatMessage("tool", children),
}}
Text={(part) => <>{part.text}</>}
/>
);
}
​

Installed dependencies

Installed with this agent because the visible component depends on them; they are support files, not separate product surfaces.

Behavior hooksrc/registry/hooks/use-chat-message.tsHook

Raw code

Primary installed agent source

Component
src/registry/sources/refined/chat-message.tsx
"use client";
​
import type { ComponentProps } from "react";
import { createContext, useContext } from "react";
​
import type { ChatMessageProps } from "@/components/refined-ui/contracts";
import { useChatMessage } from "@/components/refined-ui/hooks/use-chat-message";
import { cn } from "@/components/refined-ui/lib/cn";
import { skinSlot } from "@/components/refined-ui/skin";
​
type ChatMessageContextValue = ReturnType<typeof useChatMessage>;
​
const ChatMessageContext = createContext<ChatMessageContextValue | null>(null);
​
function useChatMessageContext() {
const context = useContext(ChatMessageContext);
if (!context) throw new Error("ChatMessage compound components must be rendered inside <ChatMessage>.");
return context;
}
​
export function ChatMessage({
from,
state = "idle",
density = "comfortable",
tone = "neutral",
className,
children,
...props
}: ChatMessageProps) {
const message = useChatMessage({ from, state, density, tone });
​
return (
<ChatMessageContext.Provider value={message}>
{/* fx anchor candidate: "chat-message:stream-layer" — anchors are added on demand (see /architecture#extension-contract);
persistent-state fx (streaming glow, tone washes) need no anchor: style the emitted data-state/data-tone in CSS. */}
<article
data-ui="agent"
data-component="chat-message"
data-role={from}
data-state={state}
data-density={density}
data-tone={tone}
className={cn("w-full", skinSlot("chat-message", { role: from, state, density, tone }), className)}
{...props}
>
{children}
</article>
</ChatMessageContext.Provider>
);
}
​
export type ChatMessageRowProps = ComponentProps<"div">;
​
export function ChatMessageRow({ className, children, ...props }: ChatMessageRowProps) {
const message = useChatMessageContext();
​
return (
<div
className={cn(
"group flex w-full gap-2",
message.isUser ? "justify-end" : "justify-start",
message.isCompact ? "py-1" : "py-2",
className,
)}
{...props}
>
{children}
</div>
);
}
​
export type ChatMessageAvatarProps = ComponentProps<"div">;
​
export function ChatMessageAvatar({ className, ...props }: ChatMessageAvatarProps) {
return (
<div
className={cn(
"mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border bg-card text-meta text-muted-foreground",
skinSlot("chat-message-avatar", {}),
className,
)}
{...props}
/>
);
}
​
export type ChatMessageBodyProps = ComponentProps<"div">;
​
export function ChatMessageBody({ className, ...props }: ChatMessageBodyProps) {
const message = useChatMessageContext();
​
return <div className={cn("min-w-0", message.isUser ? "max-w-[76%]" : "max-w-[80%] flex-1", className)} {...props} />;
}
​
export type ChatMessageHeaderProps = ComponentProps<"div">;
​
export function ChatMessageHeader({ className, ...props }: ChatMessageHeaderProps) {
return <div className={cn("mb-1 flex items-center gap-2 px-1 text-meta text-muted-foreground", className)} {...props} />;
}
​
export type ChatMessageContentProps = ComponentProps<"div">;
​
export function ChatMessageContent({ className, ...props }: ChatMessageContentProps) {
const message = useChatMessageContext();
​
return (
<div
data-ui="agent"
data-slot="chat-message-content"
data-role={message.from}
className={cn(
"text-body leading-5",
message.isUser &&
"rounded-field rounded-se-lg bg-primary px-[var(--padding-x)] py-[var(--padding-y)] text-primary-foreground shadow-sm",
// Streaming assistant reply: shimmer-text sweep IS the "generating" indicator (replaces caret dot, which it'd hide anyway); settles to --foreground when done.
// Pack can swap streaming paint via chat-message-streaming slot.
message.isAssistant && (message.isStreaming ? (skinSlot("chat-message-streaming", {}) ?? "shimmer-text") : "text-foreground"),
skinSlot("chat-message-content", { role: message.from }),
className,
)}
{...props}
/>
);
}
​
export type ChatMessageActionsProps = ComponentProps<"div">;
​
export function ChatMessageActions({ className, ...props }: ChatMessageActionsProps) {
return <div className={cn("mt-1 flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100", className)} {...props} />;
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Usage
  • Dependencies
  • Source
Does this render the active setup?
Mastra
MessageFactory maps parts into the installed component.
active_setupsuccess
mastra + refined-ui
adaptive source