Skip to content

MessageBubble

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

A versatile message bubble component for building chat interfaces and messaging UIs. MessageBubble supports different sender types, delivery statuses, avatars, color variants, and custom styling through CSS Shadow Parts.

Examples

Vue
Lit
React
Live Preview

Interactive Chat Demo

Type a message and click Send to add it to the conversation. This demonstrates real-world integration with VueInput and VueButton.

No messages yet. Start the conversation!
Send
Add Bot ReplyClear All

Basic Message Bubbles

Messages can be sent from "me" or "them" with author, time, and optional footer.

Delivery Status

Use the footer prop to display delivery status indicators.

Color Variants

Different semantic variants for various message types.

Success

Warning

Danger

Info

Neutral

Monochrome

With Avatar URLs

Display user avatars using the avatarUrl prop.

Custom Avatar Component

Use the avatar slot to provide custom avatar components with more control.

Long Content

Message bubbles adapt to longer content with proper text wrapping.

View Vue Code
<template>
  <section>
    <!-- Interactive Chat Demo -->
    <div class="mbe4">
      <h2>Interactive Chat Demo</h2>
      <p>Type a message and click Send to add it to the conversation. This demonstrates real-world integration with VueInput and VueButton.</p>
    </div>
    <div class="chat-demo-container mbe6">
      <!-- Messages Container -->
      <div class="messages-container">
        <TransitionGroup name="message-fade">
          <VueMessageBubble
            v-for="message in messages"
            :key="message.id"
            :from="message.from"
            :message="message.text"
            :author="message.author"
            :time="message.time"
            :avatar-url="message.avatarUrl"
            :footer="message.footer"
            class="message-item"
          />
        </TransitionGroup>

        <div
          v-if="messages.length === 0"
          class="empty-state"
        >
          No messages yet. Start the conversation!
        </div>
      </div>

      <!-- Input Area -->
      <div class="input-area">
        <VueInput
          v-model:value="newMessage"
          placeholder="Type your message..."
          rounded
          @keyup.enter="sendMessage"
        >
          <template #addon-left>
            <MessageCircle
              :size="18"
              color="var(--ag-primary)"
            />
          </template>
        </VueInput>

        <VueButton
          variant="primary"
          shape="rounded"
          @click="sendMessage"
          :disabled="!newMessage.trim()"
          class="send-button"
        >
          <Send :size="18" />
          <span class="mis1">Send</span>
        </VueButton>
      </div>

      <!-- Quick Actions -->
      <div class="quick-actions">
        <VueButton
          size="sm"
          shape="rounded"
          :bordered="true"
          @click="addBotResponse"
          class="mie2"
        >
          <Bot :size="16" />
          <span class="mis1">Add Bot Reply</span>
        </VueButton>

        <VueButton
          size="sm"
          shape="rounded"
          variant="danger"
          :bordered="true"
          @click="clearMessages"
        >
          <Trash2 :size="16" />
          <span class="mis1">Clear All</span>
        </VueButton>
      </div>
    </div>

    <!-- Basic Example -->
    <div class="mbe4">
      <h2>Basic Message Bubbles</h2>
      <p>Messages can be sent from "me" or "them" with author, time, and optional footer.</p>
    </div>
    <div class="message-container mbe6">
      <VueMessageBubble
        from="them"
        message="Hey, are we still on for lunch?"
        author="Sarah"
        time="11:30 AM"
      />
      <VueMessageBubble
        from="me"
        message="Yes! Where should we go?"
        author="Me"
        time="11:32 AM"
      />
      <VueMessageBubble
        from="them"
        message="How about that new Italian place?"
        author="Sarah"
        time="11:33 AM"
      />
      <VueMessageBubble
        from="me"
        message="Sounds perfect. See you there at 12:30."
        author="Me"
        time="11:35 AM"
      />
    </div>

    <!-- Delivery Status -->
    <div class="mbe4">
      <h2>Delivery Status</h2>
      <p>Use the footer prop to display delivery status indicators.</p>
    </div>
    <div class="message-container mbe6">
      <VueMessageBubble
        from="me"
        message="Hey, did you get my last message?"
        author="Me"
        time="2:30 PM"
        footer="Delivered"
      />
      <VueMessageBubble
        from="me"
        message="Just checking in!"
        author="Me"
        time="2:31 PM"
        footer="Seen"
      />
      <VueMessageBubble
        from="them"
        message="Yes, I saw it! Thanks for sending."
        author="Sarah"
        time="2:32 PM"
        footer="Read"
      />
      <VueMessageBubble
        from="me"
        message="Great! Let's catch up later."
        author="Me"
        time="2:33 PM"
        footer="Sending..."
      />
    </div>

    <!-- Color Variants -->
    <div class="mbe4">
      <h2>Color Variants</h2>
      <p>Different semantic variants for various message types.</p>
    </div>
    <div class="message-container mbe6">
      <div class="variant-section">
        <h3>Success</h3>
        <VueMessageBubble
          from="them"
          message="Payment completed successfully!"
          author="System"
          time="10:02 AM"
          variant="success"
        />
        <VueMessageBubble
          from="me"
          message="Great, thanks for confirming"
          author="Me"
          time="10:03 AM"
          variant="success"
        />
      </div>

      <div class="variant-section">
        <h3>Warning</h3>
        <VueMessageBubble
          from="them"
          message="Your session will expire in 5 minutes"
          author="System"
          time="10:04 AM"
          variant="warning"
        />
        <VueMessageBubble
          from="me"
          message="I'll save my work now"
          author="Me"
          time="10:05 AM"
          variant="warning"
        />
      </div>

      <div class="variant-section">
        <h3>Danger</h3>
        <VueMessageBubble
          from="them"
          message="Error: Unable to process your request"
          author="System"
          time="10:06 AM"
          variant="danger"
        />
        <VueMessageBubble
          from="me"
          message="Let me try again"
          author="Me"
          time="10:07 AM"
          variant="danger"
        />
      </div>

      <div class="variant-section">
        <h3>Info</h3>
        <VueMessageBubble
          from="them"
          message="New features available in version 2.0"
          author="System"
          time="10:08 AM"
          variant="info"
        />
        <VueMessageBubble
          from="me"
          message="Sounds interesting!"
          author="Me"
          time="10:09 AM"
          variant="info"
        />
      </div>

      <div class="variant-section">
        <h3>Neutral</h3>
        <VueMessageBubble
          from="them"
          message="This is a neutral informational message"
          author="System"
          time="10:10 AM"
          variant="neutral"
        />
      </div>

      <div class="variant-section">
        <h3>Monochrome</h3>
        <VueMessageBubble
          from="them"
          message="Simple monochrome styling"
          author="System"
          time="10:12 AM"
          variant="monochrome"
        />
      </div>
    </div>

    <!-- With Avatars -->
    <div class="mbe4">
      <h2>With Avatar URLs</h2>
      <p>Display user avatars using the avatarUrl prop.</p>
    </div>
    <div class="message-container mbe6">
      <VueMessageBubble
        from="them"
        message="I have an avatar!"
        author="Jane Smith"
        time="10:05 AM"
        avatar-url="https://i.pravatar.cc/150?img=1"
      />
      <VueMessageBubble
        from="me"
        message="Me too!"
        author="Me"
        time="10:06 AM"
        avatar-url="https://i.pravatar.cc/150?img=2"
      />
    </div>

    <!-- Custom Avatar Slot -->
    <div class="mbe4">
      <h2>Custom Avatar Component</h2>
      <p>Use the avatar slot to provide custom avatar components with more control.</p>
    </div>
    <div class="message-container mbe6">
      <VueMessageBubble
        from="them"
        message="I'm using a custom Avatar component with text initials!"
        author="Jane Smith"
        time="10:00 AM"
      >
        <template #avatar>
          <VueAvatar
            text="JS"
            variant="info"
            size="sm"
          />
        </template>
      </VueMessageBubble>
      <VueMessageBubble
        from="me"
        message="And I'm using an Avatar with an image and different styling!"
        author="Me"
        time="10:01 AM"
      >
        <template #avatar>
          <VueAvatar
            img-src="https://i.pravatar.cc/150?img=10"
            img-alt="User avatar"
            variant="success"
            size="sm"
          />
        </template>
      </VueMessageBubble>
      <VueMessageBubble
        from="them"
        message="Avatar components give you more control over styling and variants!"
        author="Alex Chen"
        time="10:02 AM"
      >
        <template #avatar>
          <VueAvatar
            text="AC"
            variant="warning"
            size="sm"
            shape="square"
          />
        </template>
      </VueMessageBubble>
    </div>

    <!-- Long Content -->
    <div class="mbe4">
      <h2>Long Content</h2>
      <p>Message bubbles adapt to longer content with proper text wrapping.</p>
    </div>
    <div class="message-container mbe6">
      <VueMessageBubble
        from="them"
        message="This is a much longer message that demonstrates how the message bubble handles extended content. It should wrap properly and maintain good readability across multiple lines of text."
        author="Alex"
        time="10:20 AM"
        avatar-url="https://i.pravatar.cc/150?img=4"
      />
      <VueMessageBubble
        from="me"
        message="I can write long messages too! Here's a detailed response that spans multiple lines and shows how the bubble adapts to different content lengths while maintaining its visual hierarchy."
        author="Me"
        time="10:21 AM"
        footer="Delivered"
        avatar-url="https://i.pravatar.cc/150?img=8"
      />
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { VueMessageBubble } from "agnosticui-core/message-bubble/vue";
import { VueAvatar } from "agnosticui-core/avatar/vue";
import VueInput from "agnosticui-core/input/vue";
import VueButton from "agnosticui-core/button/vue";
import { MessageCircle, Send, Bot, Trash2 } from "lucide-vue-next";

export default defineComponent({
  name: "MessageBubbleExamples",
  components: {
    VueMessageBubble,
    VueAvatar,
    VueInput,
    VueButton,
    MessageCircle,
    Send,
    Bot,
    Trash2,
  },
  data() {
    return {
      newMessage: "",
      messages: [] as Array<{
        id: number;
        from: "me" | "them";
        text: string;
        author: string;
        time: string;
        avatarUrl: string;
        footer: string;
      }>,
      messageIdCounter: 1,
      botResponses: [
        "That's interesting! Tell me more.",
        "I understand what you mean.",
        "Thanks for sharing that!",
        "Great point!",
        "I appreciate your message.",
        "That makes sense to me.",
        "Interesting perspective!",
      ],
    };
  },
  methods: {
    sendMessage() {
      if (!this.newMessage.trim()) return;

      const now = new Date();
      const time = now.toLocaleTimeString("en-US", {
        hour: "numeric",
        minute: "2-digit",
        hour12: true,
      });

      this.messages.push({
        id: this.messageIdCounter++,
        from: "me",
        text: this.newMessage,
        author: "Me",
        time: time,
        avatarUrl: "https://i.pravatar.cc/150?img=8",
        footer: "Sent",
      });

      // Clear input
      this.newMessage = "";

      // Auto-scroll to bottom
      this.$nextTick(() => {
        const container = this.$el.querySelector(".messages-container");
        if (container) {
          container.scrollTop = container.scrollHeight;
        }
      });
    },

    addBotResponse() {
      const now = new Date();
      const time = now.toLocaleTimeString("en-US", {
        hour: "numeric",
        minute: "2-digit",
        hour12: true,
      });

      const randomResponse =
        this.botResponses[Math.floor(Math.random() * this.botResponses.length)];

      this.messages.push({
        id: this.messageIdCounter++,
        from: "them",
        text: randomResponse,
        author: "AI Assistant",
        time: time,
        avatarUrl: "https://i.pravatar.cc/150?img=5",
        footer: "Read",
      });

      // Auto-scroll to bottom
      this.$nextTick(() => {
        const container = this.$el.querySelector(".messages-container");
        if (container) {
          container.scrollTop = container.scrollHeight;
        }
      });
    },

    clearMessages() {
      if (confirm("Are you sure you want to clear all messages?")) {
        this.messages = [];
        this.messageIdCounter = 1;
      }
    },
  },
});
</script>

<style scoped>
/* Chat Demo Styles */
.chat-demo-container {
  max-width: 700px;
  border: 1px solid var(--vp-c-divider);
  border-radius: 12px;
  padding: 20px;
  background-color: var(--vp-c-bg);
}

.messages-container {
  min-height: 400px;
  max-height: 500px;
  overflow-y: auto;
  padding: 20px;
  background: var(--vp-c-bg-soft);
  border: 1px solid var(--vp-c-divider);
  border-radius: 12px;
  margin-bottom: 16px;
}

.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 360px;
  color: var(--vp-c-text-3);
  font-style: italic;
}

.message-fade-enter-active {
  animation: message-fade-in var(--ag-motion-medium, 0.2s) ease-out;
}

.message-fade-leave-active {
  animation: message-fade-out var(--ag-motion-fast, 0.15s) ease-in;
}

.message-fade-move {
  transition: transform var(--ag-motion-medium, 0.2s) ease;
}

@keyframes message-fade-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes message-fade-out {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.95);
  }
}

.input-area {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
  align-items: flex-end;
}

.input-area > :first-child {
  flex: 1;
}

.send-button {
  flex-shrink: 0;
}

.quick-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Scrollbar styling */
.messages-container::-webkit-scrollbar {
  width: 8px;
}

.messages-container::-webkit-scrollbar-track {
  background: var(--vp-c-bg);
  border-radius: 4px;
}

.messages-container::-webkit-scrollbar-thumb {
  background: var(--vp-c-divider);
  border-radius: 4px;
}

.messages-container::-webkit-scrollbar-thumb:hover {
  background: var(--vp-c-text-3);
}

/* Standard Example Styles */
.message-container {
  max-width: 600px;
  border: 1px solid var(--vp-c-divider);
  padding: 20px;
  border-radius: 8px;
  background-color: var(--vp-c-bg-soft);
}

.variant-section {
  margin-bottom: 24px;
}

.variant-section:last-child {
  margin-bottom: 0;
}

.variant-section h4 {
  margin: 0 0 12px 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--vp-c-text-2);
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/message-bubble';
import 'agnosticui-core/avatar';
import 'agnosticui-core/input';
import 'agnosticui-core/button';
import 'agnosticui-core/icon';

export class MessageBubbleLitExamples extends LitElement {
  static properties = {
    newMessage: { type: String },
    messages: { type: Array },
    messageIdCounter: { type: Number }
  };

  constructor() {
    super();
    this.newMessage = '';
    this.messages = [];
    this.messageIdCounter = 1;
    this.botResponses = [
      "That's interesting! Tell me more.",
      "I understand what you mean.",
      "Thanks for sharing that!",
      "Great point!",
      "I appreciate your message.",
      "That makes sense to me.",
      "Interesting perspective!",
    ];
  }

  // Render in light DOM to access global utility classes
  createRenderRoot() {
    return this;
  }

  sendMessage() {
    if (!this.newMessage.trim()) return;

    const now = new Date();
    const time = now.toLocaleTimeString("en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });

    this.messages = [...this.messages, {
      id: this.messageIdCounter++,
      from: "me",
      text: this.newMessage,
      author: "Me",
      time: time,
      avatarUrl: "https://i.pravatar.cc/150?img=8",
      footer: "Sent",
    }];

    this.newMessage = '';

    // Auto-scroll to bottom
    setTimeout(() => {
      const container = this.querySelector('.messages-container');
      if (container) {
        container.scrollTop = container.scrollHeight;
      }
    }, 0);
  }

  addBotResponse() {
    const now = new Date();
    const time = now.toLocaleTimeString("en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });

    const randomResponse =
      this.botResponses[Math.floor(Math.random() * this.botResponses.length)];

    this.messages = [...this.messages, {
      id: this.messageIdCounter++,
      from: "them",
      text: randomResponse,
      author: "AI Assistant",
      time: time,
      avatarUrl: "https://i.pravatar.cc/150?img=5",
      footer: "Read",
    }];

    // Auto-scroll to bottom
    setTimeout(() => {
      const container = this.querySelector('.messages-container');
      if (container) {
        container.scrollTop = container.scrollHeight;
      }
    }, 0);
  }

  clearMessages() {
    if (confirm("Are you sure you want to clear all messages?")) {
      this.messages = [];
      this.messageIdCounter = 1;
    }
  }

  handleKeyUp(e) {
    if (e.key === 'Enter') {
      this.sendMessage();
    }
  }

  handleInput(e) {
    this.newMessage = e.target.value;
  }

  render() {
    return html`
      <section>
        <!-- Interactive Chat Demo -->
        <div class="mbe4">
          <h2>Interactive Chat Demo</h2>
          <p>Type a message and click Send to add it to the conversation. This demonstrates real-world integration with ag-input and ag-button.</p>
        </div>
        <div class="chat-demo-container mbe6">
          <!-- Messages Container -->
          <div class="messages-container">
            ${this.messages.map(message => html`
              <ag-message-bubble
                from=${message.from}
                message=${message.text}
                author=${message.author}
                time=${message.time}
                avatar-url=${message.avatarUrl}
                footer=${message.footer}
                class="message-item"
              ></ag-message-bubble>
            `)}

            ${this.messages.length === 0 ? html`
              <div class="empty-state">
                No messages yet. Start the conversation!
              </div>
            ` : ''}
          </div>

          <!-- Input Area -->
          <div class="input-area">
            <ag-input
              .value=${this.newMessage}
              @input=${this.handleInput}
              @keyup=${this.handleKeyUp}
              placeholder="Type your message..."
              rounded
            >
              <ag-icon slot="addon-left" size="18">
                <svg viewBox="0 0 24 24" fill="none" stroke="var(--ag-primary)" stroke-width="2">
                  <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
                </svg>
              </ag-icon>
            </ag-input>

            <ag-button
              variant="primary"
              shape="rounded"
              @click=${this.sendMessage}
              ?disabled=${!this.newMessage.trim()}
              class="send-button"
            >
              <ag-icon size="18" no-fill>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <line x1="22" y1="2" x2="11" y2="13"></line>
                  <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
                </svg>
              </ag-icon>
              <span class="mis1">Send</span>
            </ag-button>
          </div>

          <!-- Quick Actions -->
          <div class="quick-actions">
            <ag-button
              size="sm"
              shape="rounded"
              bordered
              @click=${this.addBotResponse}
              class="mie2"
            >
              <ag-icon size="16" no-fill>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
                  <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
                </svg>
              </ag-icon>
              <span class="mis1">Add Bot Reply</span>
            </ag-button>

            <ag-button
              size="sm"
              shape="rounded"
              variant="danger"
              bordered
              @click=${this.clearMessages}
            >
              <ag-icon size="16" no-fill>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <polyline points="3 6 5 6 21 6"></polyline>
                  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                </svg>
              </ag-icon>
              <span class="mis1">Clear All</span>
            </ag-button>
          </div>
        </div>

        <!-- Basic Example -->
        <div class="mbe4">
          <h2>Basic Message Bubbles</h2>
          <p>Messages can be sent from "me" or "them" with author, time, and optional footer.</p>
        </div>
        <div class="message-container mbe6">
          <ag-message-bubble
            from="them"
            message="Hey, are we still on for lunch?"
            author="Sarah"
            time="11:30 AM"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="Yes! Where should we go?"
            author="Me"
            time="11:32 AM"
          ></ag-message-bubble>
          <ag-message-bubble
            from="them"
            message="How about that new Italian place?"
            author="Sarah"
            time="11:33 AM"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="Sounds perfect. See you there at 12:30."
            author="Me"
            time="11:35 AM"
          ></ag-message-bubble>
        </div>

        <!-- Delivery Status -->
        <div class="mbe4">
          <h2>Delivery Status</h2>
          <p>Use the footer prop to display delivery status indicators.</p>
        </div>
        <div class="message-container mbe6">
          <ag-message-bubble
            from="me"
            message="Hey, did you get my last message?"
            author="Me"
            time="2:30 PM"
            footer="Delivered"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="Just checking in!"
            author="Me"
            time="2:31 PM"
            footer="Seen"
          ></ag-message-bubble>
          <ag-message-bubble
            from="them"
            message="Yes, I saw it! Thanks for sending."
            author="Sarah"
            time="2:32 PM"
            footer="Read"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="Great! Let's catch up later."
            author="Me"
            time="2:33 PM"
            footer="Sending..."
          ></ag-message-bubble>
        </div>

        <!-- Color Variants -->
        <div class="mbe4">
          <h2>Color Variants</h2>
          <p>Different semantic variants for various message types.</p>
        </div>
        <div class="message-container mbe6">
          <div class="variant-section">
            <h3>Success</h3>
            <ag-message-bubble
              from="them"
              message="Payment completed successfully!"
              author="System"
              time="10:02 AM"
              variant="success"
            ></ag-message-bubble>
            <ag-message-bubble
              from="me"
              message="Great, thanks for confirming"
              author="Me"
              time="10:03 AM"
              variant="success"
            ></ag-message-bubble>
          </div>

          <div class="variant-section">
            <h3>Warning</h3>
            <ag-message-bubble
              from="them"
              message="Your session will expire in 5 minutes"
              author="System"
              time="10:04 AM"
              variant="warning"
            ></ag-message-bubble>
            <ag-message-bubble
              from="me"
              message="I'll save my work now"
              author="Me"
              time="10:05 AM"
              variant="warning"
            ></ag-message-bubble>
          </div>

          <div class="variant-section">
            <h3>Danger</h3>
            <ag-message-bubble
              from="them"
              message="Error: Unable to process your request"
              author="System"
              time="10:06 AM"
              variant="danger"
            ></ag-message-bubble>
            <ag-message-bubble
              from="me"
              message="Let me try again"
              author="Me"
              time="10:07 AM"
              variant="danger"
            ></ag-message-bubble>
          </div>

          <div class="variant-section">
            <h3>Info</h3>
            <ag-message-bubble
              from="them"
              message="New features available in version 2.0"
              author="System"
              time="10:08 AM"
              variant="info"
            ></ag-message-bubble>
            <ag-message-bubble
              from="me"
              message="Sounds interesting!"
              author="Me"
              time="10:09 AM"
              variant="info"
            ></ag-message-bubble>
          </div>

          <div class="variant-section">
            <h3>Neutral</h3>
            <ag-message-bubble
              from="them"
              message="This is a neutral informational message"
              author="System"
              time="10:10 AM"
              variant="neutral"
            ></ag-message-bubble>
          </div>

          <div class="variant-section">
            <h3>Monochrome</h3>
            <ag-message-bubble
              from="them"
              message="Simple monochrome styling"
              author="System"
              time="10:12 AM"
              variant="monochrome"
            ></ag-message-bubble>
          </div>
        </div>

        <!-- With Avatars -->
        <div class="mbe4">
          <h2>With Avatar URLs</h2>
          <p>Display user avatars using the avatarUrl prop.</p>
        </div>
        <div class="message-container mbe6">
          <ag-message-bubble
            from="them"
            message="I have an avatar!"
            author="Jane Smith"
            time="10:05 AM"
            avatar-url="https://i.pravatar.cc/150?img=1"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="Me too!"
            author="Me"
            time="10:06 AM"
            avatar-url="https://i.pravatar.cc/150?img=2"
          ></ag-message-bubble>
        </div>

        <!-- Custom Avatar Slot -->
        <div class="mbe4">
          <h2>Custom Avatar Component</h2>
          <p>Use the avatar slot to provide custom avatar components with more control.</p>
        </div>
        <div class="message-container mbe6">
          <ag-message-bubble
            from="them"
            message="I'm using a custom Avatar component with text initials!"
            author="Jane Smith"
            time="10:00 AM"
          >
            <ag-avatar slot="avatar" text="JS" variant="info" size="sm"></ag-avatar>
          </ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="And I'm using an Avatar with an image and different styling!"
            author="Me"
            time="10:01 AM"
          >
            <ag-avatar
              slot="avatar"
              img-src="https://i.pravatar.cc/150?img=10"
              img-alt="User avatar"
              variant="success"
              size="sm"
            ></ag-avatar>
          </ag-message-bubble>
          <ag-message-bubble
            from="them"
            message="Avatar components give you more control over styling and variants!"
            author="Alex Chen"
            time="10:02 AM"
          >
            <ag-avatar
              slot="avatar"
              text="AC"
              variant="warning"
              size="sm"
              shape="square"
            ></ag-avatar>
          </ag-message-bubble>
        </div>

        <!-- Long Content -->
        <div class="mbe4">
          <h2>Long Content</h2>
          <p>Message bubbles adapt to longer content with proper text wrapping.</p>
        </div>
        <div class="message-container mbe6">
          <ag-message-bubble
            from="them"
            message="This is a much longer message that demonstrates how the message bubble handles extended content. It should wrap properly and maintain good readability across multiple lines of text."
            author="Alex"
            time="10:20 AM"
            avatar-url="https://i.pravatar.cc/150?img=4"
          ></ag-message-bubble>
          <ag-message-bubble
            from="me"
            message="I can write long messages too! Here's a detailed response that spans multiple lines and shows how the bubble adapts to different content lengths while maintaining its visual hierarchy."
            author="Me"
            time="10:21 AM"
            footer="Delivered"
            avatar-url="https://i.pravatar.cc/150?img=8"
          ></ag-message-bubble>
        </div>
      </section>

      <style>
        /* Chat Demo Styles */
        .chat-demo-container {
          max-width: 700px;
          border: 1px solid var(--vp-c-divider);
          border-radius: 12px;
          padding: 20px;
          background-color: var(--vp-c-bg);
        }

        .messages-container {
          min-height: 400px;
          max-height: 500px;
          overflow-y: auto;
          padding: 20px;
          background: var(--vp-c-bg-soft);
          border: 1px solid var(--vp-c-divider);
          border-radius: 12px;
          margin-bottom: 16px;
        }

        .empty-state {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 100%;
          min-height: 360px;
          color: var(--vp-c-text-3);
          font-style: italic;
        }

        .input-area {
          display: flex;
          gap: 12px;
          margin-bottom: 16px;
          align-items: flex-end;
        }

        .input-area > :first-child {
          flex: 1;
        }

        .send-button {
          flex-shrink: 0;
        }

        .quick-actions {
          display: flex;
          gap: 8px;
          flex-wrap: wrap;
        }

        /* Scrollbar styling */
        .messages-container::-webkit-scrollbar {
          width: 8px;
        }

        .messages-container::-webkit-scrollbar-track {
          background: var(--vp-c-bg);
          border-radius: 4px;
        }

        .messages-container::-webkit-scrollbar-thumb {
          background: var(--vp-c-divider);
          border-radius: 4px;
        }

        .messages-container::-webkit-scrollbar-thumb:hover {
          background: var(--vp-c-text-3);
        }

        /* Standard Example Styles */
        .message-container {
          max-width: 600px;
          border: 1px solid var(--vp-c-divider);
          padding: 20px;
          border-radius: 8px;
          background-color: var(--vp-c-bg-soft);
        }

        .variant-section {
          margin-bottom: 24px;
        }

        .variant-section:last-child {
          margin-bottom: 0;
        }

        .variant-section h4 {
          margin: 0 0 12px 0;
          font-size: 13px;
          font-weight: 600;
          color: var(--vp-c-text-2);
        }
      </style>
    `;
  }
}

// Register the custom element
customElements.define('message-bubble-lit-examples', MessageBubbleLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { useState } from "react";
import { ReactMessageBubble } from "agnosticui-core/message-bubble/react";
import { ReactAvatar } from "agnosticui-core/avatar/react";
import { ReactInput } from "agnosticui-core/input/react";
import { ReactButton } from "agnosticui-core/button/react";
import { ReactIcon } from "agnosticui-core/icon/react";

export default function MessageBubbleReactExamples() {
  const [newMessage, setNewMessage] = useState("");
  const [messages, setMessages] = useState([]);
  const [messageIdCounter, setMessageIdCounter] = useState(1);

  const botResponses = [
    "That's interesting! Tell me more.",
    "I understand what you mean.",
    "Thanks for sharing that!",
    "Great point!",
    "I appreciate your message.",
    "That makes sense to me.",
    "Interesting perspective!",
  ];

  const sendMessage = () => {
    if (!newMessage.trim()) return;

    const now = new Date();
    const time = now.toLocaleTimeString("en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });

    setMessages([
      ...messages,
      {
        id: messageIdCounter,
        from: "me",
        text: newMessage,
        author: "Me",
        time: time,
        avatarUrl: "https://i.pravatar.cc/150?img=8",
        footer: "Sent",
      },
    ]);

    setMessageIdCounter(messageIdCounter + 1);
    setNewMessage("");

    // Auto-scroll to bottom
    setTimeout(() => {
      const container = document.querySelector(".messages-container");
      if (container) {
        container.scrollTop = container.scrollHeight;
      }
    }, 0);
  };

  const addBotResponse = () => {
    const now = new Date();
    const time = now.toLocaleTimeString("en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });

    const randomResponse =
      botResponses[Math.floor(Math.random() * botResponses.length)];

    setMessages([
      ...messages,
      {
        id: messageIdCounter,
        from: "them",
        text: randomResponse,
        author: "AI Assistant",
        time: time,
        avatarUrl: "https://i.pravatar.cc/150?img=5",
        footer: "Read",
      },
    ]);

    setMessageIdCounter(messageIdCounter + 1);

    // Auto-scroll to bottom
    setTimeout(() => {
      const container = document.querySelector(".messages-container");
      if (container) {
        container.scrollTop = container.scrollHeight;
      }
    }, 0);
  };

  const clearMessages = () => {
    if (window.confirm("Are you sure you want to clear all messages?")) {
      setMessages([]);
      setMessageIdCounter(1);
    }
  };

  const handleKeyUp = (e) => {
    if (e.key === "Enter") {
      sendMessage();
    }
  };

  return (
    <section>
      {/* Interactive Chat Demo */}
      <div className="mbe4">
        <h2>Interactive Chat Demo</h2>
        <p>
          Type a message and click Send to add it to the conversation. This
          demonstrates real-world integration with ReactInput and ReactButton.
        </p>
      </div>
      <div className="chat-demo-container mbe6">
        {/* Messages Container */}
        <div className="messages-container">
          {messages.map((message) => (
            <ReactMessageBubble
              key={message.id}
              from={message.from}
              message={message.text}
              author={message.author}
              time={message.time}
              avatarUrl={message.avatarUrl}
              footer={message.footer}
              className="message-item"
            />
          ))}

          {messages.length === 0 && (
            <div className="empty-state">
              No messages yet. Start the conversation!
            </div>
          )}
        </div>

        {/* Input Area */}
        <div className="input-area">
          <ReactInput
            value={newMessage}
            onChange={(e) => setNewMessage(e.target.value)}
            onKeyUp={handleKeyUp}
            placeholder="Type your message..."
            rounded
          >
            <ReactIcon slot="addon-left" size="18">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="var(--ag-primary)"
                strokeWidth="2"
              >
                <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
              </svg>
            </ReactIcon>
          </ReactInput>

          <ReactButton
            variant="primary"
            shape="rounded"
            onClick={sendMessage}
            disabled={!newMessage.trim()}
            className="send-button"
          >
            <ReactIcon size="18" noFill>
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
              >
                <line x1="22" y1="2" x2="11" y2="13"></line>
                <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
              </svg>
            </ReactIcon>
            <span className="mis1">Send</span>
          </ReactButton>
        </div>

        {/* Quick Actions */}
        <div className="quick-actions">
          <ReactButton
            size="sm"
            shape="rounded"
            bordered
            onClick={addBotResponse}
            className="mie2"
          >
            <ReactIcon size="16" noFill>
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
              >
                <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
                <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
              </svg>
            </ReactIcon>
            <span className="mis1">Add Bot Reply</span>
          </ReactButton>

          <ReactButton
            size="sm"
            shape="rounded"
            variant="danger"
            bordered
            onClick={clearMessages}
          >
            <ReactIcon size="16" noFill>
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
              >
                <polyline points="3 6 5 6 21 6"></polyline>
                <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
              </svg>
            </ReactIcon>
            <span className="mis1">Clear All</span>
          </ReactButton>
        </div>
      </div>

      {/* Basic Example */}
      <div className="mbe4">
        <h2>Basic Message Bubbles</h2>
        <p>
          Messages can be sent from "me" or "them" with author, time, and
          optional footer.
        </p>
      </div>
      <div className="message-container mbe6">
        <ReactMessageBubble
          from="them"
          message="Hey, are we still on for lunch?"
          author="Sarah"
          time="11:30 AM"
        />
        <ReactMessageBubble
          from="me"
          message="Yes! Where should we go?"
          author="Me"
          time="11:32 AM"
        />
        <ReactMessageBubble
          from="them"
          message="How about that new Italian place?"
          author="Sarah"
          time="11:33 AM"
        />
        <ReactMessageBubble
          from="me"
          message="Sounds perfect. See you there at 12:30."
          author="Me"
          time="11:35 AM"
        />
      </div>

      {/* Delivery Status */}
      <div className="mbe4">
        <h2>Delivery Status</h2>
        <p>Use the footer prop to display delivery status indicators.</p>
      </div>
      <div className="message-container mbe6">
        <ReactMessageBubble
          from="me"
          message="Hey, did you get my last message?"
          author="Me"
          time="2:30 PM"
          footer="Delivered"
        />
        <ReactMessageBubble
          from="me"
          message="Just checking in!"
          author="Me"
          time="2:31 PM"
          footer="Seen"
        />
        <ReactMessageBubble
          from="them"
          message="Yes, I saw it! Thanks for sending."
          author="Sarah"
          time="2:32 PM"
          footer="Read"
        />
        <ReactMessageBubble
          from="me"
          message="Great! Let's catch up later."
          author="Me"
          time="2:33 PM"
          footer="Sending..."
        />
      </div>

      {/* Color Variants */}
      <div className="mbe4">
        <h2>Color Variants</h2>
        <p>Different semantic variants for various message types.</p>
      </div>
      <div className="message-container mbe6">
        <div className="variant-section">
          <h3>Success</h3>
          <ReactMessageBubble
            from="them"
            message="Payment completed successfully!"
            author="System"
            time="10:02 AM"
            variant="success"
          />
          <ReactMessageBubble
            from="me"
            message="Great, thanks for confirming"
            author="Me"
            time="10:03 AM"
            variant="success"
          />
        </div>

        <div className="variant-section">
          <h3>Warning</h3>
          <ReactMessageBubble
            from="them"
            message="Your session will expire in 5 minutes"
            author="System"
            time="10:04 AM"
            variant="warning"
          />
          <ReactMessageBubble
            from="me"
            message="I'll save my work now"
            author="Me"
            time="10:05 AM"
            variant="warning"
          />
        </div>

        <div className="variant-section">
          <h3>Danger</h3>
          <ReactMessageBubble
            from="them"
            message="Error: Unable to process your request"
            author="System"
            time="10:06 AM"
            variant="danger"
          />
          <ReactMessageBubble
            from="me"
            message="Let me try again"
            author="Me"
            time="10:07 AM"
            variant="danger"
          />
        </div>

        <div className="variant-section">
          <h3>Info</h3>
          <ReactMessageBubble
            from="them"
            message="New features available in version 2.0"
            author="System"
            time="10:08 AM"
            variant="info"
          />
          <ReactMessageBubble
            from="me"
            message="Sounds interesting!"
            author="Me"
            time="10:09 AM"
            variant="info"
          />
        </div>

        <div className="variant-section">
          <h3>Neutral</h3>
          <ReactMessageBubble
            from="them"
            message="This is a neutral informational message"
            author="System"
            time="10:10 AM"
            variant="neutral"
          />
        </div>

        <div className="variant-section">
          <h3>Monochrome</h3>
          <ReactMessageBubble
            from="them"
            message="Simple monochrome styling"
            author="System"
            time="10:12 AM"
            variant="monochrome"
          />
        </div>
      </div>

      {/* With Avatars */}
      <div className="mbe4">
        <h2>With Avatar URLs</h2>
        <p>Display user avatars using the avatarUrl prop.</p>
      </div>
      <div className="message-container mbe6">
        <ReactMessageBubble
          from="them"
          message="I have an avatar!"
          author="Jane Smith"
          time="10:05 AM"
          avatarUrl="https://i.pravatar.cc/150?img=1"
        />
        <ReactMessageBubble
          from="me"
          message="Me too!"
          author="Me"
          time="10:06 AM"
          avatarUrl="https://i.pravatar.cc/150?img=2"
        />
      </div>

      {/* Custom Avatar Slot */}
      <div className="mbe4">
        <h2>Custom Avatar Component</h2>
        <p>
          Use the avatar slot to provide custom avatar components with more
          control.
        </p>
      </div>
      <div className="message-container mbe6">
        <ReactMessageBubble
          from="them"
          message="I'm using a custom Avatar component with text initials!"
          author="Jane Smith"
          time="10:00 AM"
        >
          <ReactAvatar slot="avatar" text="JS" variant="info" size="sm" />
        </ReactMessageBubble>
        <ReactMessageBubble
          from="me"
          message="And I'm using an Avatar with an image and different styling!"
          author="Me"
          time="10:01 AM"
        >
          <ReactAvatar
            slot="avatar"
            imgSrc="https://i.pravatar.cc/150?img=10"
            imgAlt="User avatar"
            variant="success"
            size="sm"
          />
        </ReactMessageBubble>
        <ReactMessageBubble
          from="them"
          message="Avatar components give you more control over styling and variants!"
          author="Alex Chen"
          time="10:02 AM"
        >
          <ReactAvatar
            slot="avatar"
            text="AC"
            variant="warning"
            size="sm"
            shape="square"
          />
        </ReactMessageBubble>
      </div>

      {/* Long Content */}
      <div className="mbe4">
        <h2>Long Content</h2>
        <p>
          Message bubbles adapt to longer content with proper text wrapping.
        </p>
      </div>
      <div className="message-container mbe6">
        <ReactMessageBubble
          from="them"
          message="This is a much longer message that demonstrates how the message bubble handles extended content. It should wrap properly and maintain good readability across multiple lines of text."
          author="Alex"
          time="10:20 AM"
          avatarUrl="https://i.pravatar.cc/150?img=4"
        />
        <ReactMessageBubble
          from="me"
          message="I can write long messages too! Here's a detailed response that spans multiple lines and shows how the bubble adapts to different content lengths while maintaining its visual hierarchy."
          author="Me"
          time="10:21 AM"
          footer="Delivered"
          avatarUrl="https://i.pravatar.cc/150?img=8"
        />
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add MessageBubble

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <div>
    <VueMessageBubble
      from="them"
      message="Hello, how are you?"
      author="John Doe"
      time="10:00 AM"
    />

    <VueMessageBubble
      from="me"
      message="I'm doing great, thanks!"
      author="Me"
      time="10:01 AM"
    />

    <VueMessageBubble
      from="them"
      message="Check out this feature!"
      author="Jane"
      time="10:05 AM"
      avatar-url="https://i.pravatar.cc/150?img=1"
    />

    <VueMessageBubble
      from="me"
      message="Message with status"
      author="Me"
      time="10:10 AM"
      footer="Delivered ✓"
    />

    <VueMessageBubble
      from="them"
      message="Payment successful!"
      author="System"
      time="10:15 AM"
      variant="success"
    />
    <VueMessageBubble
      from="them"
      message="Using custom avatar component"
      author="Alex"
      time="10:20 AM"
    >
      <template #avatar>
        <VueAvatar text="AC" variant="info" size="sm" />
      </template>
    </VueMessageBubble>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { VueMessageBubble } from 'agnosticui-core/message-bubble/vue';
import { VueAvatar } from 'agnosticui-core/avatar/vue';

export default defineComponent({
  components: { VueMessageBubble, VueAvatar }
});
</script>
React
tsx
import { ReactMessageBubble } from 'agnosticui-core/message-bubble/react';
import { ReactAvatar } from 'agnosticui-core/avatar/react';

export default function Example() {
  return (
    <div>
      <ReactMessageBubble
        from="them"
        message="Hello, how are you?"
        author="John Doe"
        time="10:00 AM"
      />

      <ReactMessageBubble
        from="me"
        message="I'm doing great, thanks!"
        author="Me"
        time="10:01 AM"
      />

      <ReactMessageBubble
        from="them"
        message="Check out this feature!"
        author="Jane"
        time="10:05 AM"
        avatarUrl="https://i.pravatar.cc/150?img=1"
      />

      <ReactMessageBubble
        from="me"
        message="Message with status"
        author="Me"
        time="10:10 AM"
        footer="Delivered ✓"
      />

      <ReactMessageBubble
        from="them"
        message="Payment successful!"
        author="System"
        time="10:15 AM"
        variant="success"
      />
    </div>
  );
}
Lit (Web Components)
html
<script type="module">
  import 'agnosticui-core/message-bubble';
</script>

<ag-message-bubble
  from="them"
  message="Hello, how are you?"
  author="John Doe"
  time="10:00 AM"
></ag-message-bubble>

<ag-message-bubble
  from="me"
  message="I'm doing great, thanks!"
  author="Me"
  time="10:01 AM"
></ag-message-bubble>

<ag-message-bubble
  from="them"
  message="Check out this feature!"
  author="Jane"
  time="10:05 AM"
  avatar-url="https://i.pravatar.cc/150?img=1"
></ag-message-bubble>

<ag-message-bubble
  from="me"
  message="Message with status"
  author="Me"
  time="10:10 AM"
  footer="Delivered ✓"
></ag-message-bubble>

<ag-message-bubble
  from="them"
  message="Payment successful!"
  author="System"
  time="10:15 AM"
  variant="success"
></ag-message-bubble>

Props

PropTypeDefaultDescription
from'me' | 'them''them'Indicates the sender of the message. Affects bubble alignment and styling.
messagestring''The text content of the message.
timestring''Timestamp to display with the message.
authorstring''Name of the message sender.
avatarUrlstring''URL for the sender's avatar image.
footerstring''Footer text, typically used for delivery status (e.g., "Delivered", "Seen").
variant'default' | 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'monochrome''default'Color variant for semantic styling.

Slots

SlotDescription
defaultMain content slot for the message text (alternative to message prop).
headerOptional header content above the message.
footerOptional footer content below the message (alternative to footer prop).
avatarCustom avatar content, allowing use of Avatar components or custom elements.

CSS Custom Properties

The MessageBubble component can be styled using CSS custom properties (variables):

css
ag-message-bubble {
  --ag-message-bubble-bg: #f0f0f0;
  --ag-message-bubble-color: #333;
  --ag-message-bubble-padding: 12px 16px;
  --ag-message-bubble-border-radius: 18px;
  --ag-message-bubble-max-width: 70%;
}

CSS Shadow Parts

For advanced customization, MessageBubble exposes CSS Shadow Parts that allow you to style internal elements:

Vue Example
vue
<template>
  <div>
    <VueMessageBubble
      class="custom-bubble"
      from="them"
      message="This message bubble is custom styled using CSS Shadow Parts!"
      author="CSS Wizard"
      time="10:00 AM"
    />
    <VueMessageBubble
      from="me"
      message="This is a normal message bubble from me."
      author="Me"
      time="10:05 AM"
    />
  </div>
</template>

<style>
.custom-bubble::part(ag-message-bubble) {
  background-color: #4a90e2;
  color: white;
  border-radius: 20px;
  font-family: 'Comic Sans MS', cursive, sans-serif;
  padding: 16px 20px;
  box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
}
</style>
Available Shadow Parts
css
::part(ag-message-bubble) {
}

::part(ag-message-bubble-avatar) {
}

::part(ag-message-bubble-content) {
}

::part(ag-message-bubble-author) {
}

::part(ag-message-bubble-time) {
}

::part(ag-message-bubble-footer) {
}

Color Variants

MessageBubble includes semantic color variants for different message types:

  • default - Standard message styling
  • success - For positive notifications (green)
  • warning - For cautionary messages (yellow/orange)
  • danger - For errors or critical alerts (red)
  • info - For informational messages (blue)
  • neutral - For neutral system messages (gray)
  • monochrome - Minimal, monochrome styling

Accessibility

The MessageBubble component is built with accessibility in mind:

  • Uses semantic HTML structure for proper screen reader support
  • Supports keyboard navigation when interactive elements are included
  • Maintains appropriate color contrast ratios across all variants
  • Author and timestamp information is properly associated with messages
  • Avatar images include alt text support when using the avatarUrl prop

Best Practices

  • Always provide meaningful author information for screen reader users
  • Use time prop to give context about when messages were sent
  • Ensure custom avatar content includes appropriate alt text
  • Use semantic variant props to convey message importance visually and contextually
  • For delivery status, use clear text in the footer prop (e.g., "Delivered", "Read")

Examples

Complete Chat Interface

Full Example
vue
<template>
  <div class="chat-container">
    <VueMessageBubble
      from="them"
      message="Hey! How's the project going?"
      author="Sarah Johnson"
      time="2:30 PM"
      avatar-url="https://i.pravatar.cc/150?img=5"
    />
    
    <VueMessageBubble
      from="me"
      message="Great! Just finished the API integration."
      author="Me"
      time="2:32 PM"
      footer="Delivered ✓"
      avatar-url="https://i.pravatar.cc/150?img=8"
    />
    
    <VueMessageBubble
      from="them"
      message="That's awesome! Can you send me the documentation?"
      author="Sarah Johnson"
      time="2:33 PM"
      avatar-url="https://i.pravatar.cc/150?img=5"
    />
    
    <VueMessageBubble
      from="me"
      message="Sure, I'll email it right now."
      author="Me"
      time="2:35 PM"
      footer="Seen ✓✓"
      avatar-url="https://i.pravatar.cc/150?img=8"
    />
    
    <VueMessageBubble
      from="them"
      message="Perfect! Thanks so much! 🎉"
      author="Sarah Johnson"
      time="2:36 PM"
      avatar-url="https://i.pravatar.cc/150?img=5"
    />
  </div>
</template>

<style>
.chat-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  background: #f9f9f9;
  border-radius: 8px;
}
</style>

System Notifications

Example
vue
<template>
  <div class="notifications">
    <VueMessageBubble
      from="them"
      message="Your file has been uploaded successfully!"
      author="System"
      time="3:00 PM"
      variant="success"
    />
    
    <VueMessageBubble
      from="them"
      message="Warning: Your storage is almost full (90% used)"
      author="System"
      time="3:05 PM"
      variant="warning"
    />
    
    <VueMessageBubble
      from="them"
      message="Error: Failed to sync with cloud storage"
      author="System"
      time="3:10 PM"
      variant="danger"
    />
    
    <VueMessageBubble
      from="them"
      message="New feature: Dark mode is now available!"
      author="System"
      time="3:15 PM"
      variant="info"
    />
  </div>
</template>