A Quartz v5 transformer plugin that turns Markdown chat syntax into chat-style bubbles. Chats are only collapsible when the Markdown explicitly opts in.
The primary syntax is an attribute-based fenced block. It is explicit, Markdown-like, and leaves room for future metadata without inventing new delimiters.
```chat from="Sam" time="10:42"
Hey, are you joining?
```
```chat status="read"
Yes, in two minutes.
```When from is omitted, it defaults to self (displayed as “Me”) and the message is placed on the
right. Explicit senders default to the left. from="self" and from="me" are equivalent, and the
legacy side attribute can still override the inferred side.
Supported attributes:
| Attribute | Values |
|---|---|
from |
Sender label; defaults to self / “Me” |
side |
Legacy override: left, right |
avatar |
Image URL |
time |
Display timestamp |
status |
sent, delivered, read, failed |
variant |
default, system, warning, success |
tone |
neutral, muted, emphasis |
thread |
Thread identifier added as data-thread |
collapsed |
true, false; makes the thread collapsible and initially collapsed |
collapsible |
true, false; adds a toggle without forcing initial collapse |
maxHeight |
Collapsed height for that thread, in pixels |
All options follow the same space-separated key="value" format:
```chat status="sent" time="2026-08-27T10:42:00" avatar="./assets/avatar.png" variant="warning" collapsed="true" maxHeight="300"
This message uses the supported metadata options.
```Collapse a long chat only when you ask for it:
```chat from="Sam" collapsed="true" maxHeight="420"
This long message starts collapsed.
```Directive-style blocks are available for shorter authoring:
::chat{from="Ava" time="9:15"}
Can you review this?
::
::chat-reply{from="Me" status="read"}
Reviewed. Looks good.
::Aliases: ::message, ::reply, ::bubble, and ::dm.
For quick conversations, use a :::chat container with < for left-side messages and > for
right-side messages.
:::chat {.collapsed maxHeight="420"}
< Sam [10:42]: Hey.
> Me [read]: Hi.
< Sam: Quick question.
:::The bracket metadata becomes a timestamp unless it matches a known status.
npx quartz plugin add github:jerlendds/quartz-chat-bubblesThen enable the plugin in quartz.config.yaml:
plugins:
- source: github:jerlendds/quartz-chat-bubbles
enabled: true
options:
maxHeight: 600
defaultCollapsed: falseFor TypeScript configuration:
import * as ExternalPlugin from "./.quartz/plugins";
export default {
plugins: {
transformers: [
ExternalPlugin.ChatBubbles({
maxHeight: 600,
defaultSide: "left",
}),
],
},
};| Option | Type | Default |
|---|---|---|
maxHeight |
number |
600 |
defaultCollapsed |
boolean |
false |
defaultSide |
"left" | "right" |
"left" |
threadClass |
string |
undefined |
expandLabel |
string |
"Show full conversation" |
collapseLabel |
string |
"Collapse conversation" |
enableTranscriptSyntax |
boolean |
true |
enableDirectiveSyntax |
boolean |
true |
defaultCollapsed only applies to threads that are explicitly marked collapsible. Unmarked chats
never collapse automatically.
Message bodies support paragraphs, line breaks, inline code, emphasis, strong text, and basic links. Because the plugin emits HTML during Quartz's text transform step, it intentionally does not run full Markdown parsing inside each bubble.
npm install
npm test
npm run buildCommit the generated dist/ output before publishing.