Dashral Pro is on the wayGet 30% off at launch

Dashral

Message

One message in a conversation with an AI assistant, with a cursor while it streams and Copy, Regenerate and feedback under it.

$ pnpm dlx shadcn@latest add https://dashral.com/r/message.json
Who are our biggest customers this quarter?
Northwind is your biggest account this quarter: $18,400 across 6 orders, 22% of revenue. Sagara Resorts follows with $11,250, and Kopi Senja with $7,900.

Installation

$ pnpm dlx shadcn@latest add https://dashral.com/r/message.json

Adds the component, the components it builds on, its npm packages and the theme tokens it uses. Using Dashral often? Add the registry to components.json once:

components.json
{
  "registries": {
    "@dashral": "https://dashral.com/r/{name}.json"
  }
}

Then add components by name:

$ pnpm dlx shadcn@latest add @dashral/message

Usage

example.tsx
import { Message } from "@/components/dashral/message"

<Message
  from="assistant"
  onRegenerate={() => api.chat.regenerate()}
  onFeedbackChange={(feedback) => api.chat.feedback(feedback)}
>
  Revenue rose 12% this month, led by the Team plan.
</Message>

Examples

Streaming, done and failed

A cursor and no actions while it streams; an error says so, with Try again.

Pulling last month's orders from the store and grouping them by
Last month you had 1,284 orders, 8.1% more than the month before. Online orders grew fastest.
Comparing refunds by product,

Your own rendering

A table and a list inside, your own mark as the avatar, and copyText for a version written for pasting.

Here's revenue by plan this month. Team brings in the most, with fewer than half Personal's customers:

PlanCustomersRevenue
Personal214$21,186
Team88$21,912
Extended12$5,988
  • Team customers pay 2.5× more on average.
  • Extended is small, but every one of its 12 customers renewed.

API reference

Message

Also takes every prop of <article>(opens in a new tab).

PropTypeDefault
from"user" | "assistant"

Required. Yours sit at the end in a bubble; the assistant's open flat beside its avatar.

—
childrenReactNode

Text (line breaks kept) or your own rendering: Markdown, a table, a chart.

—
namestring

The sender, for the avatar's initials and screen readers.

"You" / "Assistant"
avatarstring | ReactNode | false

An image URL or your own mark. The assistant shows a sparkle by default, you nothing; false hides it.

—
timeDate | string

A Date is written in the locale; a string as given ("2 min ago").

—
status"streaming" | "done" | "error"

Streaming shows a cursor and holds the actions back; error says so.

"done"
copyTextstring

What Copy copies; the text as it reads on screen by default.

—
onRegenerate() => void

Shows Regenerate, and Try again after an error.

—
feedback / defaultFeedback"up" | "down" | null

Good or bad response, controlled or initial.

null
onFeedbackChange(feedback) => void

Shows the two feedback buttons. Pressing the chosen one again clears it.

—
actionsReactNode

More buttons, after the built-in ones.

—
showActions"hover" | "always" | false

Hover shows them with the pointer or focus on the message, and always on touch screens.

"hover"
errorReactNode

What an error says.

"Couldn't finish this answer."
classNames{ content?, avatar?, actions?, meta? }

Classes for each part.

—
classNamestring

Extra classes, merged with cn() so yours win.

—

Accessibility

  • Each message is an article named by its sender; put them in an element with role="log" (Conversation does) so new ones are announced.
  • While it streams, aria-busy holds screen readers back until the answer is whole, instead of reading every word.
  • The action buttons are named (Copy, Regenerate, Good response, Bad response) with tooltips; the feedback buttons say which one is pressed.
  • Hover-only actions still show on keyboard focus, and always on touch screens.
  • dir="auto" on the text, so an answer in Arabic reads right to left in an English app.

Motion

  • The streaming cursor pulses, and stands still with reduced motion. The actions fade in over --duration-fast.