Kempo UI Icon Kempo UI

Kempo UI

Kempo UI Icon
Base Components
Utils

Chat

Table of Contents

A chat-room widget: scrollable history of messages above, a markdown input + Send button below. The input is a <k-markdown-editor> — users type markdown (with a Preview tab to see the rendered output before sending), and the message is rendered to HTML via the vendored marked parser (CommonMark + GFM, including tables and task lists). The rendered HTML runs through sanitizeHtml on its way into the message bubble so any inline HTML the user types is filtered against the allow-list — <script>, <iframe>, etc. are always stripped.

Basic Usage

The component fires a send event when the user submits. Listen for it and call chat.addMessage(...) for incoming replies. Outgoing messages are added automatically.

<k-chat id="basicChat"></k-chat>
<script>
const chat = document.getElementById('basicChat');
chat.addEventListener('send', (e) => {
console.log('Sent:', e.detail.markdown);
setTimeout(() => {
chat.addMessage({
type: 'incoming',
html: 'Got it!',
sender: 'Bot'
});
}, 500);
});
</script>

Enter Behavior

By default the chat matches Slack/Discord/Messages: Enter sends, Shift+Enter inserts a newline. Add the enter-newline attribute to flip it — Enter inserts a newline, Shift+Enter sends.

<k-chat enter-newline></k-chat>

Message Status

Add show-status and outgoing messages get an iMessage-style status indicator below the bubble. The four statuses are sending, delivered, read, and failed. When the user submits, the message is added with status: 'sending'; advance it via chat.updateMessage(id, { status }) as the backend confirms.

The attribute takes one of two display modes:

Following iMessage convention, the indicator only renders on the most recent outgoing message — older delivered/read messages stay clean. sending and failed always show regardless of position so the user knows about in-flight or stuck messages immediately.

<k-chat id="ackChat" show-status></k-chat>
<script type="module">
await customElements.whenDefined('k-chat');
const chat = document.getElementById('ackChat');
chat.addEventListener('send', async (e) => {
// Simulate a server round-trip
await new Promise(r => setTimeout(r, 800));
chat.updateMessage(e.detail.id, {
status: 'delivered'
});
await new Promise(r => setTimeout(r, 1200));
chat.updateMessage(e.detail.id, {
status: 'read'
});
});
</script>

Icon Mode

Pass show-status="icons" for compact icon-only badges (spinner / single check / double check / red error) instead of text labels — closer to WhatsApp / Telegram styling.

<k-chat id="iconChat" show-status="icons"></k-chat>
<script type="module">
await customElements.whenDefined('k-chat');
const chat = document.getElementById('iconChat');
chat.addEventListener('send', async (e) => {
await new Promise(r => setTimeout(r, 800));
chat.updateMessage(e.detail.id, {
status: 'delivered'
});
await new Promise(r => setTimeout(r, 1200));
chat.updateMessage(e.detail.id, {
status: 'read'
});
});
</script>

Disabled

The disabled attribute prevents user interaction. Useful while waiting for connection or rate-limiting.

<k-chat disabled></k-chat>

JavaScript Reference

Constructor

Extends ShadowComponent
new Chat()

Requirements

Properties

enterNewline: Boolean

Inverts the default Enter-key behavior. Default is false (Slack-style: Enter sends, Shift+Enter inserts a newline). When true: Enter inserts a newline and Shift+Enter sends. Syncs to enter-newline attribute.

showStatus: String

Enables and configures the per-message status indicator. Possible values: null/empty (off, default), "text" (small grey labels — the default mode when the attribute is set without a value), or "icons" (spinner/check/double-check/error glyphs). The indicator only appears on the most recent outgoing message, except for sending and failed which always show. Outgoing messages submitted via the input start as sending — advance them via updateMessage(id, { status }) as the backend confirms. Syncs to show-status attribute.

placeholder: String

Placeholder text for the input. Default "Type a message...". Syncs to placeholder attribute.

disabled: Boolean

When true, the input and Send button are disabled. Syncs to disabled attribute.

messages: Array (read-only state)

The current array of messages. Each entry is { id, type, html, status, sender, timestamp }. Don't mutate directly — use the public methods.

Methods

addMessage(msg): string

Append a message and return its id. msg object accepts { id, type, html, status, sender, timestamp }; missing fields are filled in (auto-generated id, type defaults to "incoming", etc.). HTML is sanitized.

updateMessage(id, updates): boolean

Update an existing message's html, status, sender, or timestamp. Returns true if a message with that id was found.

removeMessage(id): boolean

Remove a message. Returns true if found.

clear(): void

Remove every message.

send(): string | null

Programmatically submit whatever is currently in the input. Returns the new outgoing message's id, or null if the input was empty.

CSS Variables

VariableDefaultDescription
--window_min_height16remMinimum height of the message window (the chat won't collapse below this even when empty)
--window_max_height32remMaximum height of the message window before it scrolls internally
--chat_padding0.5remPadding inside both the message window and the input row
--chat_gap0.5remGap between messages and gap between the editor and the Send button
--height13remInitial height of the embedded markdown editor; the user can drag the resize handle below it to grow or shrink past this
--message_max_width75%Max width of a single message bubble
--message_radius1remBorder radius of message bubbles
--bubble_padding0.5rem 0.75remPadding inside bubbles
--bubble_bg__incomingvar(--c_bg__alt)Background color of incoming bubbles
--bubble_bg__outgoingvar(--c_primary)Background color of outgoing bubbles
--bubble_tc__outgoingwhiteText color of outgoing bubbles

Events

send

Fires when the user submits. detail contains { id, html, markdown }html is the rendered+sanitized output (the same string used in the bubble) and markdown is the raw source the user typed. The outgoing message is already in the conversation by the time the event fires — if you need to roll it back (e.g. on a network failure), call updateMessage(id, { status: 'failed' }) or removeMessage(id).

Security Notes

The frontend sanitization in this component is not a substitute for server-side sanitization.

Every message that enters the chat (whether from the user's editor, from addMessage(), or from updateMessage()) runs through sanitizeHtml before it is added to the DOM. That strips <script>, <style>, <iframe>, event-handler attributes, and unsafe URL schemes — but it only protects content that flows through this component.

If you persist messages on a server and rebroadcast them to other users, the server must independently sanitize before storing and again before sending. A determined attacker can submit raw HTTP without ever loading your client code.