Chat Overlay
A scrolling chat display overlay that shows messages from all connected platforms in real time. Demonstrates useLumioEvent with the "chat:message" event, message queuing, and theme-aware styling.
Project structure
chat-overlay/
├── lumio.config.json
├── src/
│ ├── editor.tsx
│ └── layer.tsx
└── package.json
lumio.config.json
{
"extensionId": "ext_placeholder",
"name": "Chat Overlay",
"version": "1.0.0",
"targets": ["layer", "editor"],
"permissions": ["events:read"]
}
src/editor.tsx
import { Lumio, Box, Text, Select, Toggle, useLumioConfig } from "@zaflun/lumio-sdk";
interface ChatConfig {
maxMessages: number;
showBadges: boolean;
showPlatformIcon: boolean;
fontSize: number;
}
const DEFAULT_CONFIG: ChatConfig = {
maxMessages: 20,
showBadges: true,
showPlatformIcon: true,
fontSize: 14,
};
const FONT_SIZE_OPTIONS = [
{ value: "12", label: "Small (12px)" },
{ value: "14", label: "Medium (14px)" },
{ value: "16", label: "Large (16px)" },
{ value: "20", label: "Extra Large (20px)" },
];
function ChatEditor() {
const { config, setConfig } = useLumioConfig<ChatConfig>();
const cfg = { ...DEFAULT_CONFIG, ...config };
return (
<Box style={{ padding: 20, display: "flex", flexDirection: "column", gap: 16 }}>
<Text content="Chat Overlay Settings" variant="heading" />
<Select
label="Font Size"
value={String(cfg.fontSize)}
options={FONT_SIZE_OPTIONS}
onChange={(v) => setConfig({ fontSize: Number(v) })}
/>
<Toggle
label="Show badges"
checked={cfg.showBadges}
onChange={(v) => setConfig({ showBadges: v })}
/>
<Toggle
label="Show platform icon"
checked={cfg.showPlatformIcon}
onChange={(v) => setConfig({ showPlatformIcon: v })}
/>
</Box>
);
}
Lumio.render(<ChatEditor />, { target: "editor" });
src/layer.tsx
import { Lumio, Box, Text, useLumioEvent, useLumioConfig, useLumioTheme } from "@zaflun/lumio-sdk";
import { useState, useEffect, useRef } from "react";
interface ChatMessage {
id: string;
platform: string;
userName: string;
message: string;
badges: string[];
timestamp: number;
}
interface ChatConfig {
maxMessages: number;
showBadges: boolean;
showPlatformIcon: boolean;
fontSize: number;
}
const PLATFORM_COLORS: Record<string, string> = {
twitch: "#9146ff",
youtube: "#ff0000",
kick: "#53fc19",
trovo: "#26c3a8",
};
const PLATFORM_LABELS: Record<string, string> = {
twitch: "T",
youtube: "YT",
kick: "K",
trovo: "TR",
};
const DEFAULT_CONFIG: ChatConfig = {
maxMessages: 20,
showBadges: true,
showPlatformIcon: true,
fontSize: 14,
};
function ChatOverlay() {
const event = useLumioEvent("chat:message");
const { config } = useLumioConfig<ChatConfig>();
const theme = useLumioTheme();
const [messages, setMessages] = useState<ChatMessage[]>([]);
const listRef = useRef<HTMLDivElement>(null);
const cfg = { ...DEFAULT_CONFIG, ...config };
useEffect(() => {
if (!event) return;
const msg: ChatMessage = {
id: `${event.timestamp}-${event.data.userId}`,
platform: event.data.platform,
userName: event.data.userName,
message: event.data.message,
badges: event.data.badges ?? [],
timestamp: Date.now(),
};
setMessages((prev) => [...prev, msg].slice(-cfg.maxMessages));
}, [event, cfg.maxMessages]);
// Auto-scroll to bottom
useEffect(() => {
if (listRef.current) {
listRef.current.scrollTop = listRef.current.scrollHeight;
}
}, [messages]);
return (
<Box
style={{
position: "absolute",
bottom: 20,
left: 20,
width: 340,
maxHeight: 480,
display: "flex",
flexDirection: "column",
gap: 0,
overflow: "hidden",
}}
>
<Box
ref={listRef}
style={{
display: "flex",
flexDirection: "column",
gap: 4,
overflowY: "auto",
scrollbarWidth: "none",
}}
>
{messages.map((msg) => (
<Box
key={msg.id}
style={{
background: theme.mode === "dark"
? "rgba(0,0,0,0.7)"
: "rgba(255,255,255,0.85)",
borderRadius: 6,
padding: "6px 10px",
display: "flex",
flexDirection: "column",
gap: 2,
}}
>
<Box style={{ display: "flex", alignItems: "center", gap: 6 }}>
{cfg.showPlatformIcon && (
<Box
style={{
background: PLATFORM_COLORS[msg.platform] ?? "#6366f1",
borderRadius: 3,
padding: "1px 4px",
fontSize: 9,
fontWeight: 700,
color: "#fff",
}}
>
<Text
content={PLATFORM_LABELS[msg.platform] ?? msg.platform.toUpperCase()}
variant="default"
style={{ fontSize: 9, fontWeight: 700, color: "#fff" }}
/>
</Box>
)}
<Text
content={msg.userName}
variant="default"
style={{ fontSize: cfg.fontSize, fontWeight: 600, color: "#6366f1" }}
/>
</Box>
<Text
content={msg.message}
variant="default"
style={{ fontSize: cfg.fontSize, lineHeight: 1.4 }}
/>
</Box>
))}
</Box>
</Box>
);
}
Lumio.render(<ChatOverlay />, { target: "layer" });
Key concepts demonstrated
useLumioEvent("chat:message")— receives chat messages from all connected platforms (Twitch, YouTube, Kick, Trovo)- Message queue — maintains a bounded list of recent messages using
slice(-maxMessages)to prevent unbounded growth useLumioTheme()— adapts background color to the streamer's chosen light/dark themeuseLumioConfig()— reads editor-set settings like font size and badge visibility- Auto-scroll — a
useRef+useEffectpattern keeps the chat list scrolled to the latest message
The chat:message event payload
interface ChatMessageEvent {
platform: "twitch" | "youtube" | "kick" | "trovo";
userName: string;
userId: string;
message: string;
emotes: Array<{ id: string; name: string; url: string }>;
badges: string[]; // e.g. ["subscriber", "moderator"]
color?: string; // Twitch chat color, hex
}