KQ-1778 · LIVE/CHATTER
Live Chatter
A live event chat that holds still while you read: while nothing holds it, the log's scroll chases the newest row on a spring derived from springs.glide whose stiffness scales with scrollSpeed², keeping its velocity across arrivals so a burst reads as one climb. Hovering, keyboard focus, Space or scrolling up freezes it in place while a pill rises on springs.snap counting the messages below the fold with rolling digits; choosing the pill or pressing End fast-forwards on a stiff glide under a brief vertical blur and draws the newest row's edge on springs.flick. Up and Down walk the rows, Space pauses and resumes, Tab reaches each row's Report, and slow mode, mutes and a dropped connection show in the composer and the log.
Coldbrook all-hands
Q4 roadmap · 1,284 watching
Up and Down move between messages and hold the chat still. Space pauses and resumes. End jumps to the latest message and goes live.
live · 21 messages · 1,284 watching
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/live-chatter
Props
- messages
- LiveChatterMessage[]
- default defaultLiveChatterMessages
- The stream so far, oldest first: { id, author, text, at, badges?: ("host" | "mod" | "speaker" | "staff")[], kind?: "message" | "gap", missed?, until? }. The host appends; the chat follows.
- title
- string
- default "Coldbrook all-hands"
- The broadcast's name in the header; also names the log.
- subtitle
- string
- default "Q4 roadmap"
- A short line under the title, before the viewer count.
- viewers
- number
- default 1284
- How many people are watching, grouped by hand so server and browser agree.
- connection
- "live" | "reconnecting" | "offline"
- default "live"
- Anything but live tears the log's tail with a "Connection lost" edge and holds sending with a reason in the composer.
- paused / defaultPaused
- boolean
- default — / false
- The explicit pause (Space), controlled or initial. Hover, keyboard focus and scrolling up still hold on top of it.
- onPause
- (paused, reason) => void
- default —
- The stream started or stopped holding still; reason is "hover", "focus", "key", "scroll", "pill" or "send".
- onSend
- (text) => void
- default —
- A message was sent from the composer (Enter). Append it to messages to show it; the log goes live to see it land.
- onReport
- (message) => void
- default —
- A message was reported through its row's confirm; it is already hidden for this viewer.
- slowMode
- number
- default 0
- Seconds between messages from one viewer. Shows a header chip, and after a send a ring on Send that empties over the cooldown.
- muted / mutedUntil / mutedReason
- boolean / number / string
- default false
- Replaces the composer with a calm notice: until when (ms since the epoch) and why.
- viewerName
- string
- default "Mara Quinn"
- Messages that @mention the viewer's first name get an accent wash.
- now
- number
- default defaultLiveChatterNow
- The current moment in ms since the epoch, for the connection-lost stamp. Never read from the clock during render.
- scrollSpeed
- number
- default 1
- How briskly the log follows new rows: the follow spring's stiffness scales with its square (0.5 drifts, 2 ticks).
- density
- "compact" | "regular" | "roomy"
- default "regular"
- One tight line per row at 12px, a coloured dot before each name, or an initial avatar with the name and time over the message.
- accent
- string
- default "var(--accent-bright)"
- Pill, Send, Host badge, mention wash and the newest-row edge — any CSS colour; fills take its hue at a fixed lightness.
- maxRows
- number
- default 80
- Rows kept in the DOM while live; a hold keeps up to three times as many so nothing you are reading is trimmed.
- maxLength
- number
- default 200
- Characters a message may hold; a counter shows past 80%.
- formatTime
- (ms) => string
- default UTC HH:MM
- How times are printed on rows, the mute notice and the connection stamp.
- status / onRetry
- "ready" | "loading" | "error" / () => void
- default "ready"
- Loading shows skeleton rows; error shows a message and Try again, which calls onRetry.
- height
- number
- default 540
- The surface's height in px; the log scrolls inside it.
- disabled
- boolean
- default false
- Read only: no composer and no reporting; the log still reads and pauses.
- className
- string
- default —
- Classes for the outer section.
| Prop | Type | Default | Description |
|---|---|---|---|
| messages | LiveChatterMessage[] | defaultLiveChatterMessages | The stream so far, oldest first: { id, author, text, at, badges?: ("host" | "mod" | "speaker" | "staff")[], kind?: "message" | "gap", missed?, until? }. The host appends; the chat follows. |
| title | string | "Coldbrook all-hands" | The broadcast's name in the header; also names the log. |
| subtitle | string | "Q4 roadmap" | A short line under the title, before the viewer count. |
| viewers | number | 1284 | How many people are watching, grouped by hand so server and browser agree. |
| connection | "live" | "reconnecting" | "offline" | "live" | Anything but live tears the log's tail with a "Connection lost" edge and holds sending with a reason in the composer. |
| paused / defaultPaused | boolean | — / false | The explicit pause (Space), controlled or initial. Hover, keyboard focus and scrolling up still hold on top of it. |
| onPause | (paused, reason) => void | — | The stream started or stopped holding still; reason is "hover", "focus", "key", "scroll", "pill" or "send". |
| onSend | (text) => void | — | A message was sent from the composer (Enter). Append it to messages to show it; the log goes live to see it land. |
| onReport | (message) => void | — | A message was reported through its row's confirm; it is already hidden for this viewer. |
| slowMode | number | 0 | Seconds between messages from one viewer. Shows a header chip, and after a send a ring on Send that empties over the cooldown. |
| muted / mutedUntil / mutedReason | boolean / number / string | false | Replaces the composer with a calm notice: until when (ms since the epoch) and why. |
| viewerName | string | "Mara Quinn" | Messages that @mention the viewer's first name get an accent wash. |
| now | number | defaultLiveChatterNow | The current moment in ms since the epoch, for the connection-lost stamp. Never read from the clock during render. |
| scrollSpeed | number | 1 | How briskly the log follows new rows: the follow spring's stiffness scales with its square (0.5 drifts, 2 ticks). |
| density | "compact" | "regular" | "roomy" | "regular" | One tight line per row at 12px, a coloured dot before each name, or an initial avatar with the name and time over the message. |
| accent | string | "var(--accent-bright)" | Pill, Send, Host badge, mention wash and the newest-row edge — any CSS colour; fills take its hue at a fixed lightness. |
| maxRows | number | 80 | Rows kept in the DOM while live; a hold keeps up to three times as many so nothing you are reading is trimmed. |
| maxLength | number | 200 | Characters a message may hold; a counter shows past 80%. |
| formatTime | (ms) => string | UTC HH:MM | How times are printed on rows, the mute notice and the connection stamp. |
| status / onRetry | "ready" | "loading" | "error" / () => void | "ready" | Loading shows skeleton rows; error shows a message and Try again, which calls onRetry. |
| height | number | 540 | The surface's height in px; the log scrolls inside it. |
| disabled | boolean | false | Read only: no composer and no reporting; the log still reads and pauses. |
| className | string | — | Classes for the outer section. |