ChatMessage
One message on the chat page: the client's words in a soft gray bubble on the right, Okami's answer as bare text on the left.
Playground
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage from="client">
Any chance Saturday morning?
</ChatMessage>A conversation
The client's words sit in a bubble of the card tint, its corner tucked toward them; Okami speaks on the atmosphere itself, never in a box. While a reply is on its way, the TypingIndicator takes Okami's side; quick replies follow as Chips.
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage from="client">Any chance Saturday morning?</ChatMessage>
<ChatMessage from="agent">
Hey Marek! Saturday I've got 9:30 or 11:00 for a skin fade — which works?
</ChatMessage>
<div className="flex flex-wrap gap-2">
<Chip>9:30</Chip>
<Chip>11:00</Chip>
</div>With an author
When someone other than Okami answers on its side — the studio stepping into a conversation from the Inbox — their name sits above the words, small and soft, so the client knows a person wrote it.
Belaze
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage from="client">Can I bring my dog?</ChatMessage>
<ChatMessage from="agent" author="Belaze">
Of course, she's welcome!
</ChatMessage>Failed
When a message doesn't reach Okami it stays in the thread, saying so beneath, with a ghost Button to send the same words again. Nothing is lost and nothing needs retyping.
Message not delivered.
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage
from="client"
failed
onRetry={() => resend(message)}
failedLabel={t("error")}
retryLabel={t("retry")}
>
Is Friday 10:00 still free?
</ChatMessage>With a file
A file sent with a message — an owner's photo of their price list — shows by name with a paperclip, above any words. The file itself never shows: the bubble says what was sent. On Instagram, where Okami keeps no media, it says what kind it was by its own glyph and opens the chat in Instagram, where the photo or voice note is.
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage from="client" attachment="cennik.jpg">
Here's our price list.
</ChatMessage>
<ChatMessage
from="agent"
attachment="Voice message"
attachmentIcon={Mic}
attachmentHref="https://ig.me/m/petra.nails"
/>Long messages
A message takes at most five sixths of the thread, wraps, and keeps the line breaks its sender typed.
import { ChatMessage } from "@/components/shared/chat-message";
<ChatMessage from="agent">
{"About three hours with Nina.\nFriday she's free at 9:00 — shall I book you in?"}
</ChatMessage>API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| from | "client" | "agent" | — | Who wrote it: the client, in a soft gray bubble on the right (ink at 5%, no edge or shadow); or Okami, as bare text on the left. |
| children | ReactNode | — | The message. Line breaks are kept. |
| author | string | — | Who wrote it, small above the words: the studio's name when a member answers on Okami's side. |
| attachment | string | — | A file sent with the message, by name, or what was sent ("Voice message"), above the words. With it, children may be left out. |
| attachmentIcon | Icon | Paperclip | The attachment's glyph: Image, Mic, Video and the like. |
| attachmentHref | string | — | Where the attachment is, opened in a new tab: the Instagram chat (ig.me/m/<username>). |
| failed | boolean | false | It didn't reach Okami. Shows failedLabel and the retry beneath when onRetry is set. |
| onRetry | () => void | — | Sends the same message again. |
| failedLabel | string | — | What went wrong, e.g. "Message not delivered." |
| retryLabel | string | — | The retry action, e.g. "Try again". |
| className | string | — | Layout only, on the row. |
Guidelines
- Only the client's words sit in a bubble. Okami answers as bare text on the atmosphere — the agent is the place, not a participant in a box.
- Messages rise in once, as they land, and never move again. Under
prefers-reduced-motionthey simply appear. - A failed message keeps its place and its words; the retry sends them again as they were. Never drop it silently.
- Show messages whole — no truncation, no "read more". Keep the agent's replies short enough not to need it.