ChatLauncher

A floating chat button in a corner of the page that opens a chat panel. The panel is not modal: the page stays interactive, Escape closes it while focus is inside, and focus returns to the button. The chat stays mounted while the panel is closed (keepMounted, true by default), so the conversation and a running stream survive closing. Place it with position (bottom-right or bottom-left) and offset (24px, a number or { x, y }); size the panel with panelWidth (380) and panelHeight (640), both clamped to the viewport. When the panel does not fit, or the available width is below fullScreenBreakpoint (520px), it opens full screen and page scroll is frozen (mobileFullScreen). unreadCount shows a badge on the closed button. Open state can be controlled with opened / onOpenedChange or left to defaultOpened. It renders in a portal by default; set withinPortal={false} to keep it inside a container. To add the chat to a page that is not a React app, or to isolate it from the page CSS, use mountChatLauncher(target, element, options): it renders into a Shadow DOM with its own MantineProvider, injects the stylesheets you pass in styles or styleUrls, and returns unmount. See Embedding the launcher. The previews render the launcher with withinPortal={false} inside a page frame, so it measures the frame instead of the window.

Ways to get in touch

Pass actions to turn the first click into a fan of round buttons that leave the launcher one after another: chat, a call back, a demo, an email. The kit ships no brand icons and no links of its own — every action carries its own icon, label, color and either onClick or href, so the row stays yours. An action with opensChat opens the panel, the others only call back and hand the focus back to the button. While the fan is open the button turns into a close button and the unread badge steps aside; a click outside, Escape or opening the chat closes the fan, and Escape moves the focus only when it was already inside the launcher. A disabled action stays a button, never a link. actionsMotion sends them out one after another (sequence) or almost at once (together); actionSize, actionGap and buttonSize set the diameters and the spacing. pulse draws slow rings around the closed button, and altIcon with iconAnimation lets the button alternate between the icon and an avatar: swap fades the icon out and the avatar in, flip turns the button over, cover grows the avatar over the icon. Everything stays still under prefers-reduced-motion and while the panel is open, wherever the panel was opened from.

Getting Started
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-react
Examples
Desktop and mobile
Full screen below 520px
Actions that fan out of the button
Unread badge and keepMounted
API reference
Prop
Type
Required
children
React.ReactNode
Yes
Panel content, usually `AgentChat`
opened
boolean
No
Controlled open state
defaultOpened
boolean
No
Initial open state when `opened` is not controlled, `false` by default
onOpenedChange
(opened: boolean) => void
No
Called when the button, the close button or Escape changes the open state
position
ChatLauncherPosition
No
Corner of the viewport, `bottom-right` by default
offset
ChatLauncherOffset
No
Distance from the viewport edges in px, `24` by default; drops to 12px when the panel only fits that way
panelWidth
number
No
Panel width in px, never wider than the viewport, `380` by default
panelHeight
number
No
Panel height in px, never taller than the viewport, `640` by default
title
React.ReactNode
No
Panel header title
headerActions
React.ReactNode
No
Actions in the panel header before the close button
icon
React.ReactNode
No
Button icon while the panel is closed
altIcon
React.ReactNode
No
Second face of the button, usually an avatar, shown by `iconAnimation`
iconAnimation
'none' | 'swap' | 'flip' | 'cover'
No
How the button alternates between `icon` and `altIcon` while it is closed, `none` by default
buttonSize
number
No
Diameter of the launcher button in px, `56` by default
actionsMotion
'sequence' | 'together'
No
Order the actions appear in: one after another, or all at once, `sequence` by default
pulse
boolean
No
Draws slow rings around the closed button to invite the first click
actions
LauncherAction[]
No
Actions that fan out of the button instead of opening the panel on the first click
actionSize
number
No
Diameter of an action button in px, `48` by default
actionGap
number
No
Space between the action buttons in px, `12` by default
unreadCount
number
No
Unread messages shown as a badge on the closed button, hidden at `0`
keepMounted
boolean
No
Keeps the panel content mounted while closed so the chat keeps its state, `true` by default
closeOnEscape
boolean
No
Closes the panel on Escape while focus is inside the launcher, `true` by default
mobileFullScreen
boolean
No
Opens the panel full screen when it does not fit or the available width is below `fullScreenBreakpoint`, `true` by default
fullScreenBreakpoint
number
No
Available width in px below which the panel opens full screen, `520` by default
withinPortal
boolean
No
Renders in a portal at the end of the document, `true` by default; turn off to position the launcher against a transformed container
zIndex
number
No
Stacking order of the button and the panel, `200` by default
labels
Partial<ChatLauncherLabels>
No
Overrides of the default English labels
className
string
No
Class name added to the root element
style
React.CSSProperties
No
Inline styles added to the root element
classNames
ChatLauncherClassNames
No
Class names of inner elements