Chat

Chat bubbles for messaging interfaces. Supports avatars, headers, footers, and color variants.

Basic Example

Simple chat conversation

await import('/components/data-display/chat.js');
const { tags, $ } = Lightview;
const { div, Chat } = tags;

const conversation = div({},
    Chat({ position: 'start' },
        Chat.Image({ src: 'https://i.pravatar.cc/100?img=10' }),
        Chat.Header({}, 'Obi-Wan'),
        Chat.Bubble({}, "It's over Anakin, I have the high ground."),
        Chat.Footer({}, 'Delivered')
    ),
    Chat({ position: 'end' },
        Chat.Image({ src: 'https://i.pravatar.cc/100?img=11' }),
        Chat.Header({}, 'Anakin'),
        Chat.Bubble({}, 'You underestimate my power!'),
        Chat.Footer({}, 'Seen')
    )
);

$('#example').content(conversation);
await import('/components/data-display/chat.js');
const { $, tags } = Lightview;
const { Chat, div } = tags;

const conversation = {
    tag: div,
    children: [
        {
            tag: Chat,
            attributes: { position: 'start' },
            children: [
                { tag: Chat.Image, attributes: { src: 'https://i.pravatar.cc/100?img=10' } },
                { tag: Chat.Header, children: ['Obi-Wan'] },
                { tag: Chat.Bubble, children: ["It's over Anakin, I have the high ground."] },
                { tag: Chat.Footer, children: ['Delivered'] }
            ]
        },
        {
            tag: Chat,
            attributes: { position: 'end' },
            children: [
                { tag: Chat.Image, attributes: { src: 'https://i.pravatar.cc/100?img=11' } },
                { tag: Chat.Header, children: ['Anakin'] },
                { tag: Chat.Bubble, children: ['You underestimate my power!'] },
                { tag: Chat.Footer, children: ['Seen'] }
            ]
        }
    ]
};

$('#example').content(conversation);
await import('/components/data-display/chat.js');
const { $ } = Lightview;

const conversation = {
    div: {
        children: [
            {
                Chat: {
                    position: 'start',
                    children: [
                        { ChatImage: { src: 'https://i.pravatar.cc/100?img=10' } },
                        { ChatHeader: { children: ['Obi-Wan'] } },
                        { ChatBubble: { children: ["It's over Anakin, I have the high ground."] } },
                        { ChatFooter: { children: ['Delivered'] } }
                    ]
                }
            },
            {
                Chat: {
                    position: 'end',
                    children: [
                        { ChatImage: { src: 'https://i.pravatar.cc/100?img=11' } },
                        { ChatHeader: { children: ['Anakin'] } },
                        { ChatBubble: { children: ['You underestimate my power!'] } },
                        { ChatFooter: { children: ['Seen'] } }
                    ]
                }
            }
        ]
    }
};

$('#example').content(conversation);

Typing Indicator

Chat with reactive message sending

await import('/components/data-display/chat.js');
await import('/components/actions/button.js');
const { signal, tags, $ } = Lightview;
const { div, input, Chat, Button } = tags;

const messages = signal([
    { pos: 'start', text: 'Hey! How are you?', time: '10:00' }
]);
const inputVal = signal('');

const sendMessage = () => {
    if (!inputVal.value.trim()) return;
    messages.value = [...messages.value, 
        { pos: 'end', text: inputVal.value, time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }
    ];
    inputVal.value = '';
};

const demo = div({ style: 'display: flex; flex-direction: column; gap: 1rem' },
    div({ class: 'h-48 overflow-y-auto' },
        () => messages.value.map(m => 
            Chat({ position: m.pos },
                Chat.Bubble({ color: m.pos === 'end' ? 'primary' : undefined }, m.text),
                Chat.Footer({}, m.time)
            )
        )
    ),
    div({ style: 'display: flex; gap: 0.5rem' },
        input({ 
            type: 'text', 
            class: 'input input-bordered flex-1',
            placeholder: 'Type a message...',
            value: () => inputVal.value,
            oninput: (e) => { inputVal.value = e.target.value; },
            onkeypress: (e) => { if(e.key === 'Enter') sendMessage(); }
        }),
        Button({ color: 'primary', onclick: sendMessage }, 'Send')
    )
);

$('#example').content(demo);
await import('/components/data-display/chat.js');
await import('/components/actions/button.js');
const { signal, $, tags } = Lightview;
const { Chat, Button, div, input } = tags;

const messages = signal([
    { pos: 'start', text: 'Hey! How are you?', time: '10:00' }
]);
const inputVal = signal('');

const sendMessage = () => {
    if (!inputVal.value.trim()) return;
    messages.value = [...messages.value, 
        { pos: 'end', text: inputVal.value, time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }
    ];
    inputVal.value = '';
};

const demo = {
    tag: div,
    attributes: { style: 'display: flex; flex-direction: column; gap: 1rem' },
    children: [
        {
            tag: div,
            attributes: { class: 'h-48 overflow-y-auto' },
            children: [
                () => messages.value.map(m => ({
                    tag: Chat,
                    attributes: { position: m.pos },
                    children: [
                        { tag: Chat.Bubble, attributes: { color: m.pos === 'end' ? 'primary' : undefined }, children: [m.text] },
                        { tag: Chat.Footer, children: [m.time] }
                    ]
                }))
            ]
        },
        {
            tag: div,
            attributes: { style: 'display: flex; gap: 0.5rem' },
            children: [
                {
                    tag: input,
                    attributes: {
                        type: 'text',
                        class: 'input input-bordered flex-1',
                        placeholder: 'Type a message...',
                        value: () => inputVal.value,
                        oninput: (e) => { inputVal.value = e.target.value; },
                        onkeypress: (e) => { if(e.key === 'Enter') sendMessage(); }
                    }
                },
                {
                    tag: Button,
                    attributes: { color: 'primary', onclick: sendMessage },
                    children: ['Send']
                }
            ]
        }
    ]
};

$('#example').content(demo);
await import('/components/data-display/chat.js');
await import('/components/actions/button.js');
const { signal, $ } = Lightview;

const messages = signal([
    { pos: 'start', text: 'Hey! How are you?', time: '10:00' }
]);
const inputVal = signal('');

const sendMessage = () => {
    if (!inputVal.value.trim()) return;
    messages.value = [...messages.value, 
        { pos: 'end', text: inputVal.value, time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }
    ];
    inputVal.value = '';
};

const demo = {
    div: {
        style: 'display: flex; flex-direction: column; gap: 1rem',
        children: [
            {
                div: {
                    class: 'h-48 overflow-y-auto',
                    children: [
                        () => messages.value.map(m => ({
                            Chat: {
                                position: m.pos,
                                children: [
                                    { ChatBubble: { color: m.pos === 'end' ? 'primary' : undefined, children: [m.text] } },
                                    { ChatFooter: { children: [m.time] } }
                                ]
                            }
                        }))
                    ]
                }
            },
            {
                div: {
                    style: 'display: flex; gap: 0.5rem',
                    children: [
                        {
                            input: {
                                type: 'text',
                                class: 'input input-bordered flex-1',
                                placeholder: 'Type a message...',
                                value: () => inputVal.value,
                                oninput: (e) => { inputVal.value = e.target.value; },
                                onkeypress: (e) => { if(e.key === 'Enter') sendMessage(); }
                            }
                        },
                        {
                            Button: {
                                color: 'primary',
                                onclick: sendMessage,
                                children: ['Send']
                            }
                        }
                    ]
                }
            }
        ]
    }
};

$('#example').content(demo);

Chat Props

Prop Type Default Description
position string 'start' 'start' (left) | 'end' (right)

Sub-components

Component Props Description
Chat.Image src, alt Avatar image
Chat.Header - Name/timestamp header
Chat.Bubble color Message bubble
Chat.Footer - Read status/time

Bubble Colors

Primary
Secondary
Accent
Info
Success
Warning
Error