Patterns · Overlays
SupportPanel
The rail’s Help & support panel: in-app support chat, help articles, contact options and the status page.
Chat, help articles, contacts and status
import { useMemo, useState } from 'react';
import { SupportPanel, type SupportChatClient, type SupportChatEvent } from '@rookcom/monarch-react/shell';
import { Button } from '@rookcom/monarch-react';
/** Docs only: an in-memory client that answers every message after a moment. */
function useDemoClient(): SupportChatClient {
return useMemo(() => {
const listeners = new Set<(e: SupportChatEvent) => void>();
let n = 0;
const emit = (e: SupportChatEvent) => listeners.forEach((fn) => fn(e));
return {
connect: async () => ({
messages: [
{
id: 'm1',
author: 'system',
text: 'Conversation started',
createdAt: '2026-11-04T09:58:00Z',
},
{
id: 'm2',
author: 'agent',
authorName: 'Sam',
text: 'Hi! What can we help you with today?',
createdAt: '2026-11-04T09:58:00Z',
},
],
availability: { online: true, expectedReply: 'Typically replies in a few minutes' },
}),
send: async (text) => {
n += 1;
setTimeout(() => emit({ type: 'typing', typing: true, name: 'Sam' }), 400);
setTimeout(
() =>
emit({
type: 'message',
message: {
id: `r${n}`,
author: 'agent',
authorName: 'Sam',
text: 'Thanks, looking into it now.',
createdAt: new Date().toISOString(),
},
}),
1600,
);
return { id: `v${n}`, author: 'visitor', text, createdAt: new Date().toISOString() };
},
subscribe: (fn) => {
listeners.add(fn);
return () => listeners.delete(fn);
},
};
}, []);
}
const utcTime = (d: Date) => `${d.toISOString().slice(11, 16)} UTC`;
function Basic() {
const client = useDemoClient();
const [open, setOpen] = useState(false);
return (
<>
<Button
aria-expanded={open}
aria-controls={open ? 'support-demo' : undefined}
onClick={() => setOpen(true)}
>
Help & support
</Button>
<SupportPanel
id="support-demo"
open={open}
onOpenChange={setOpen}
client={client}
formatTime={utcTime}
articles={
<ul style={{ margin: 0, paddingLeft: 'var(--mn-space-5)', lineHeight: 1.9 }}>
<li>
<a href="#getting-started">Getting started</a>
</li>
<li>
<a href="#invite-your-team">Invite your team</a>
</li>
<li>
<a href="#billing-questions">Billing questions</a>
</li>
</ul>
}
contacts={[
{
label: 'Email support',
href: 'mailto:[email protected]',
description: '[email protected]',
},
]}
status={{
href: 'https://status.example.com',
tone: 'operational',
text: 'All systems normal',
}}
helpHref="https://help.example.com"
/>
</>
);
}
<Basic />Usage examples
Help only
import { useState } from 'react';
import { SupportPanel } from '@rookcom/monarch-react/shell';
import { Button } from '@rookcom/monarch-react';
function HelpOnly() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="ghost" onClick={() => setOpen(true)}>
Get help
</Button>
<SupportPanel
open={open}
onOpenChange={setOpen}
contacts={[
{
label: 'Email support',
href: 'mailto:[email protected]',
description: '[email protected]',
},
]}
status={{ href: 'https://status.example.com' }}
messages={{ description: 'Find an answer or get in touch.' }}
/>
</>
);
}
<HelpOnly />Purpose
Every Rook app offers help from the rail’s "Help & support" item: a side panel with chat to the support team, help articles and other ways to reach them. SupportPanel is that panel on Sheet. The chat runs through the product’s Support Chat API client; Monarch makes no network calls.
Use when
- Once per app, opened from RookRail’s
supportentry (support.onOpen,open,panelId). - Help-only (no
client) where chat is not offered yet.
Avoid when
- Chat between colleagues: that is ChatDock (Rook Chat), bottom-right.
- Contextual help on one field or page: use a Tooltip, Popover or inline help text.
Design principles
- Nothing before it is asked for: the client connects on the first open, never at page load.
- Injected, not built in: the product implements
SupportChatClientover its Support Chat API with its own identity and single-flight session refresh. - Survives closing: the conversation, the draft and the live stream are kept while the panel is closed.
- Never empty: connecting, a timeout, a failure, an ended session and an away team each have their own message, with Try again where it helps.
- From the rail, beside it: a left sheet that can sit next to the rail (
inset), never a bottom-right bubble, which belongs to Rook Chat.
API reference
import { SUPPORT_MESSAGE_MAX, SupportPanel, useSupportChat } from '@rookcom/monarch-react/shell';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SUPPORT_MESSAGE_MAX | utility | client | – |
SupportPanel | component | client | .mn-layout-rail-collapsed |
useSupportChat | hook | client | – |
Forwards its ref to the sheet. Built on Sheet and Tabs from @rookcom/monarch-react.
Also exported: useSupportChat (the chat state machine, for a custom surface on the same client) and SUPPORT_MESSAGE_MAX (5000).
SupportChatClient is { connect({ signal }), send(text, { clientMessageId }), subscribe?(listener), markRead?(messageId) }.
Generated from the TypeScript declarations of packages/react/src/patterns/overlays/support-panel.
Properties
open + onOpenChange (controlled, as the rail needs) or defaultOpen.
chat replaces the built-in chat with your own surface (for example a widget host element); client is then not used.
status.tone needs status.text: colour alone says nothing.
SupportPanel
The rail's "Help & support" panel: a side sheet with in-app support chat (through the product's Support Chat API client), help articles, contact options and the status page. It is a modal dialog (Sheet): focus moves in, Escape closes, focus returns. The conversation is kept while the panel is closed. Monarch makes no network calls; the client does.
| Prop | Type | Description |
|---|---|---|
articles | ReactNode | Help articles (search, popular articles): the Help tab's first section. |
chat | ReactNode | Your own chat surface instead of the built-in one (e.g. a widget host element). |
className | string | |
client | SupportChatClient | The product's Support Chat API client. With it, the panel has a Chat tab. |
connectTimeout | number | Give up connecting after this long. Default 15000 ms; 0 waits forever. |
contacts | SupportContact[] | Other ways to reach support. |
defaultOpen | boolean | |
defaultTab | "help" | "chat" | The tab shown first. Default `chat` when there is one. |
formatTime | ((date: Date) => string) | Formats message times. Default: the browser's short time. |
helpHref | string | "Help Center" link in the footer. |
id | string | Panel id, for the rail's `support.panelId` (`aria-controls`). |
inset | string | Distance from the edge from 48rem up, so the panel opens beside the rail instead of over it, e.g. `var(--mn-layout-rail-collapsed)`. Default 0. |
messages | Partial<SupportPanelMessages> | Interface text, for translation. |
onOpenChange | ((open: boolean) => void) | |
open | boolean | Open (controlled). Wire to the rail's `support.open`. |
side | "right" | "left" | Edge the panel opens from. Default `left` (beside the rail). |
status | SupportStatus | The status page. |
Variants
| Prop | Values | Description |
|---|---|---|
content | 'chat + help (tabs)''chat only''help only' | Tabs appear when there is both a chat and help content. |
side | 'left''right' | Left (default) for the rail; inset keeps it clear of the rail from 48rem up. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Connecting | – | A status while connect runs (15 seconds at most by default). |
| Ready | – | The conversation log, availability line and composer. |
| Sending / Not sent | data-pending | A message shows "Sending…" until stored; a failure says "Not sent." with Try again (same client id). |
| Typing | – | "Sam is typing…" in a status line. |
| Away | – | Availability offline: the team will reply here later. |
| Error / ended | – | Timeout, failure, ended session or unavailable: an alert, with Try again except for unavailable. |
Interaction behaviour
- Opening connects once; Try again connects again (the previous attempt is aborted).
- Enter sends, Shift+Enter adds a line; the composer keeps focus after sending.
- Live events add replies (deduplicated by id), typing, availability and the end of a session.
- While the chat is visible, the newest support message is reported with
markRead. - The Help tab holds the articles slot and the contacts; the footer keeps the status page and Help Center one click away.
Keyboard support
| Keys | Action |
|---|---|
| Escape | Close the panel; focus returns to what opened it (the rail item). |
| Tab / Shift+Tab | Move within the panel (focus is trapped while open). |
| ← / → | Switch between Chat and Help. |
| Enter | Send the message. |
| Shift+Enter | New line in the message. |
Accessibility
- Role
- dialog (modal Sheet) labelled by its title and described by its description
- Accessible name
- "Help & support" (
messages.title).
- Focus moves into the panel on open and back to the opener on close, also when the opener is the rail item (not a SheetTrigger); without it, the visible
[data-rook-support-trigger]. - The conversation is a focusable
role="log"(polite), so new replies are announced and the history can be scrolled by keyboard. - Errors are alerts; connecting and typing are status messages.
- External links say they open in a new tab; the status dot is decorative and the state is in the link text.
- Security: Monarch makes no requests. Messages are rendered as text, never as HTML, and are capped at 5000 characters. The client owns identity, tokens and refresh; never pass a secret key to the browser.
Responsive behaviour
- A 28rem side sheet from 48rem up; below that the full screen width, with safe-area insets.
- Messages wrap long words; the composer grows to 10rem, then scrolls.
Theme support
- Sheet surface tokens; your messages use the accent solid colours, support’s the muted surface.
- The sheet’s slide-in and the contact hover are removed under reduced motion.
Do and don't
Do
- Open it from the rail and pass
openandpanelIdback to the rail foraria-expandedandaria-controls. - Implement
connectwith the abort signal, andsendidempotently byclientMessageId. - Keep the panel mounted for the whole session so the conversation survives navigation.
Don't
- Do not put a support bubble bottom-right.
- Do not connect before the first open.
- Do not show raw API errors in the chat; reject and let the panel explain.
Related components
Dependencies
Testing coverage
- Connects on first open only; labelled, described dialog; conversation log; availability; markRead.
- Enter sends, Shift+Enter is a new line; optimistic then stored.
- A failed send is marked and retried with the same client id.
- Live replies (deduplicated), typing, away and an ended session.
- Timeout aborts and offers Try again; load failure.
- Conversation and draft kept across close and reopen; focus returns to the opener.
- Chat and Help tabs (arrow keys), articles, contacts, status with text, Help Center.
- Own chat surface; help only; defaultOpen, id, inset and translation; SSR when closed.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/support-panel (and the open panel in visual.spec.ts); Cross-browser axe: /components/support-panel |
Tests: packages/react/src/patterns/overlays/support-panel