Kosmesis
Components

Message

Composes Bubble with an optional avatar, role-based layout, and an optional trailing actions row, for chat UIs.

Installation

npx kosmesis add message
pnpm dlx kosmesis add message
yarn dlx kosmesis add message
bunx kosmesis add message

This 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.

message.tsx
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/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

This 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.

message.tsx
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

PropTypeDefault
from"user" | "assistant"— required
avatarChildren— rendered before the bubble (e.g. an Avatar)
actionsChildren— rendered below the bubble, indented to align with it (e.g. Actions)

MessageGroup takes no props beyond class/children.

On this page