Components
Message
Composes Bubble with an optional avatar, role-based layout, and an optional trailing actions row, for chat UIs.
Installation
npx kosmesis add messagepnpm dlx kosmesis add messageyarn dlx kosmesis add messagebunx kosmesis add messageThis component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): bubble.
Copy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Bubble } from "./bubble";import { cn } from "@/lib/utils";export interface MessageProps { from: "user" | "assistant"; class?: string; /** Rendered before the bubble (e.g. an `Avatar`). */ avatar?: Children; /** Rendered below the bubble, indented to align with it (e.g. a row of icon action buttons). */ actions?: Children; children?: Children;}@Component()export class Message extends StatelessComponent<MessageProps> { render() { const { from, class: cls, avatar, actions, children } = this.props; return ( <div class={cn("flex flex-col gap-1.5", from === "user" && "items-end", cls)}> <div class={cn("flex items-end gap-2", from === "user" && "flex-row-reverse")}> {avatar} <Bubble variant={from === "user" ? "sent" : "received"}>{children}</Bubble> </div> {actions && <div class={cn("flex items-center gap-1", from === "user" ? "pr-10" : "pl-10")}>{actions}</div>} </div> ); }}export interface MessageGroupProps { class?: string; children?: Children;}@Component()export class MessageGroup extends StatelessComponent<MessageGroupProps> { render() { const { class: cls, children } = this.props; return <div class={cn("flex flex-col gap-3 p-4", cls)}>{children}</div>; }}Install the following dependencies:
npm install @praxisjs/csspnpm add @praxisjs/cssyarn add @praxisjs/cssbun add @praxisjs/cssThis component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): bubble.
Copy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Bubble } from "./bubble";class MessageStyles extends Stylesheet { $wrap = this.css({ display: "flex", flexDirection: "column", gap: "0.375rem" }); $wrapEnd = this.css({ alignItems: "flex-end" }); $root = this.css({ display: "flex", alignItems: "flex-end", gap: "0.5rem" }); $reversed = this.css({ flexDirection: "row-reverse" }); $actions = this.css({ display: "flex", alignItems: "center", gap: "0.25rem" }); $actionsUser = this.css({ paddingRight: "2.5rem" }); $actionsAssistant = this.css({ paddingLeft: "2.5rem" }); $group = this.css({ display: "flex", flexDirection: "column", gap: "0.75rem", padding: "1rem" });}export interface MessageProps { from: "user" | "assistant"; class?: string; /** Rendered before the bubble (e.g. an `Avatar`). */ avatar?: Children; /** Rendered below the bubble, indented to align with it (e.g. a row of icon action buttons). */ actions?: Children; children?: Children;}@Component()export class Message extends StatelessComponent<MessageProps> { @Styled(MessageStyles) $s!: MessageStyles; render() { const { from, class: cls, avatar, actions, children } = this.props; return ( <div class={cx(this.$s.$wrap, from === "user" && this.$s.$wrapEnd, cls)}> <div class={cx(this.$s.$root, from === "user" && this.$s.$reversed)}> {avatar} <Bubble variant={from === "user" ? "sent" : "received"}>{children}</Bubble> </div> {actions && ( <div class={cx(this.$s.$actions, from === "user" ? this.$s.$actionsUser : this.$s.$actionsAssistant)}>{actions}</div> )} </div> ); }}export interface MessageGroupProps { class?: string; children?: Children;}@Component()export class MessageGroup extends StatelessComponent<MessageGroupProps> { @Styled(MessageStyles) $s!: MessageStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$group, cls)}>{children}</div>; }}Examples
Usage
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Message, MessageGroup } from "@/components/ui/message";
@Component()
class Chat extends StatefulComponent {
@State() avatar = new Avatar();
render() {
return (
<MessageGroup>
<Message from="assistant" avatar={
<Avatar class="size-6"><AvatarFallback avatar={this.avatar}>AI</AvatarFallback></Avatar>
}>
How can I help you today?
</Message>
<Message from="user">What's the weather like?</Message>
</MessageGroup>
);
}
}Use with Message Scroller for a scrollable chat viewport,
Attachment for file previews within a message, and
Actions for the actions slot below.
Props
Message
| Prop | Type | Default |
|---|---|---|
from | "user" | "assistant" | — required |
avatar | Children | — rendered before the bubble (e.g. an Avatar) |
actions | Children | — rendered below the bubble, indented to align with it (e.g. Actions) |
MessageGroup takes no props beyond class/children.