KQ-1776 · VISITOR/CHAT

Visitor Chat

A website chat widget that lives in the corner of its own box, with the host page behind it. Pressing the launcher stretches it into the window on a goo layer used only while the shape moves: the window grows out of the launcher, rising on springs.snap and widening on springs.glide while its fill turns from the accent to the card and the neck between them thins until it lets go, then the crisp window takes over and its content arrives; closing runs back into the launcher on the exit ease. Inside, the header says who is on and how soon they reply, the messages are a log where an agent's reply resolves out of the typing bubble on springs.glide, the composer is a real textarea with attach and send, a failed send keeps a retry on its bubble, outside office hours the window asks for an email first, and Escape closes it and returns focus to the launcher.

KQ-1776 · VISITOR/CHAT

waylight help · press the launcher in the corner

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/visitor-chat

Props

launcherShape
"circle" | "squircle" | "pill"
default "circle"
The launcher: a circle, a rounded square, or a pill with "Chat with us". The stretch starts from its shape.
bubbleStyle
"tinted" | "solid" | "minimal"
default "tinted"
Message bubbles: a soft tint, a solid accent for the visitor, or agents as plain text.
accent
string
default "var(--accent-bright)"
Any CSS colour: the launcher, the visitor's bubbles, read marks and focus edges.
team
ChatAgent[]
default defaultChatTeam
Who answers: { id, name, role?, online, hue? }.
hours
ChatHours
default defaultChatHours
When the team answers live: { days (0 = Sunday), open, close (minutes), utcOffset (minutes), zone }.
messages / defaultMessages
ChatMessage[]
default — / []
The thread, controlled or initial: { id, from ("visitor" or an agent id), text, at, status?, attachment? }.
onMessagesChange
(messages: ChatMessage[]) => void
default —
Fires from a send, a delivery change or a retry, with every message.
typing
string | null
default null
The agent typing now, by id; their next message resolves out of the indicator.
onSend
(message: ChatMessage, meta: { email: string | null }) => void | Promise<void>
default —
A message was sent or retried. Return a promise: resolved marks it sent, rejected keeps a retry on its bubble.
onAttach
(files: File[]) => void
default —
Files were picked with the attach button; the first rides on the next message.
open / defaultOpen
boolean
default — / false
Whether the window is open, controlled or initial. A host that opens it gets the same stretch, without moving focus.
onOpenChange
(open: boolean) => void
default —
The launcher, Escape or the minimise button changed the open state.
onClose
() => void
default —
The window was closed by the visitor.
now
number
default defaultChatNow (Fri 16 Oct 2026 14:20 UTC)
The current time in ms; decides office hours. Never read from the clock.
replyMinutes
number
default 4
How soon the team usually replies, in minutes, shown in the header.
title
string
default "Waylight help"
The window's title and the dialog's name.
greeting
string
default "Hi, we're the Waylight team. Ask us anything about your workspace, billing or the API."
The team's first message, always shown at the top.
height
number
default 560
Height of the box the widget lives in, in px.
disabled
boolean
default false
Nothing can be opened or sent.
className
string
default —
Classes for the outer box.
children
ReactNode
default —
The page behind the widget.