ChatPanel
A dockable, floating, minimizable window frame for chat — an inline sidebar that pops out to a draggable, resizable window or collapses to a corner bubble.1(function ChatPanelPreview() {2 const [mode, setMode] = React.useState("docked");34 return (5 <Flex6 style={{7 width: "100%",8 height: 420,9 border: "0.5px solid var(--rs-color-border-base-primary)",10 borderRadius: "var(--rs-radius-4)",11 overflow: "hidden",12 }}13 >14 <Flex15 direction="column"
Anatomy
1import { ChatPanel } from '@raystack/apsara'23<Flex>4 <MainContent />5 <ChatPanel mode={mode} onModeChange={setMode} side="right">6 <ChatPanel.Header>7 <ChatPanel.Title>Create task in design system 2</ChatPanel.Title>8 <ChatPanel.Actions>9 {/* consumer extras, e.g. ⋯ menu */}10 <ChatPanel.MinimizeTrigger />11 <ChatPanel.ExpandTrigger />12 </ChatPanel.Actions>13 </ChatPanel.Header>14 <ChatPanel.Content>{/* Chat.Messages + PromptInput */}</ChatPanel.Content>15 <ChatPanel.Trigger /> {/* minimized bubble */}16 </ChatPanel>17</Flex>
The panel mounts once in your layout and morphs between modes with pure CSS — no portal, no remount, so chat state (scroll position, focus, streams) survives every transition:
docked— an in-flow sidebar (a flex sibling, likeSidePanel) that squeezes the main content rather than covering it.floating— the same element switched toposition: fixed: drag it by the header, resize it from any edge or corner, both viewport-clamped.minimized— the frame collapses toChatPanel.Trigger, a fixed corner bubble; clicking it restores the previous mode. If you don't render a trigger, minimized shows nothing and your app supplies its own affordance.
There is deliberately no close (×) — header actions are slottable, so apps add their own controls next to the ready-made mode triggers.
Because floating and minimized rely on position: fixed, mount the panel
near the layout root: an ancestor with transform, filter or
backdrop-filter would break fixed positioning.
API Reference
Root
Prop
Type
Header
The title bar. In floating mode it is the drag handle — pointer drags on it
move the window (interactive children like buttons are excluded, as is
anything marked data-chat-panel-no-drag).
Title
The heading (<h2>), truncated with an ellipsis.
Actions
A slot row at the end of the header for controls.
MinimizeTrigger / ExpandTrigger
Ready-made mode buttons for ChatPanel.Actions, built on IconButton.
MinimizeTrigger switches to minimized; ExpandTrigger toggles between
docked and floating with a matching icon and accessible name.
Content
The body wrapper — a flex column that gives Chat.Messages its bounded
height.
Trigger
The minimized-state corner bubble.
Prop
Type
Examples
Controlled mode
Control mode to persist it, or to open the panel from your own UI. The
floating window here is fixed to the browser viewport — pop it out and drag
its header.
1(function ControlledChatPanel() {2 const [mode, setMode] = React.useState("docked");34 return (5 <Flex direction="column" gap={4} style={{ width: "100%" }}>6 <Flex gap={3}>7 <Button8 size="small"9 variant="outline"10 color="neutral"11 onClick={() => setMode("docked")}12 >13 Dock14 </Button>15 <Button
Unread badge on the bubble
1(function MinimizedWithBadge() {2 const [mode, setMode] = React.useState("minimized");34 // The transform makes the frame the containing block for the panel's5 // fixed positioning, so this demo's trigger pins to the frame corner6 // instead of stacking on the page's other panels.7 return (8 <Flex direction="column" gap={4} style={{ width: "100%" }}>9 <Text size="small" variant="secondary">10 The minimized trigger is a slot — compose Indicator or Badge for unread11 counts. Look at the bottom-right of this frame.12 </Text>13 <Flex14 style={{15 width: "100%",
Persisting placement
Position and size are controllable for apps that restore the floating window across sessions:
1<ChatPanel2 mode={mode}3 onModeChange={setMode}4 position={saved.position}5 onPositionChange={savePosition}6 size={saved.size}7 onSizeChange={saveSize}8 minSize={{ width: 320, height: 400 }}9>10 …11</ChatPanel>
Accessibility
- The root renders an
<aside>(complementarylandmark); give it anaria-labelwhen your page has more than one. ChatPanel.Titleis a real<h2>heading.- All mode triggers are
IconButtons with descriptive, state-aware accessible names ("Minimize chat panel", "Pop out chat panel", "Dock chat panel", "Open chat"). - Mode transitions never remount the content, so keyboard focus inside the thread or composer is preserved when docking or popping out.
- Dragging clamps so the header can never leave the viewport, and the window re-clamps when the browser is resized.
- Drag and resize are pointer gestures; keep docked mode reachable (the ExpandTrigger toggle) so keyboard users can always use the inline layout.