Overlays & feedback
AI chat widget
A floating, always-available assistant: a corner FAB opens a compact, non-modal chat panel for a quick in-context question, then hands the conversation off to the full AI Chat page. Mount it once at the app root — it persists across navigation and never traps focus.
npx shadcn@latest add @orbit/ai-chat-widgetOverview
Live — open the panel, ask something (canned replies stand in for a model), and follow an artifact card toward the Assistant. In a real app the widget is fixed to the viewport corner; the demos dock it inside a canvas with mode=inline.
Capabilities
A quick surface for in-context questions that escalates to the full page when an answer needs room.
A 54px corner launcher with a pulsing ring and an unread badge opens a compact chat panel — one tap to ask in context, cleared as soon as it opens.
Right-aligned user bubbles and assistant replies with lightweight markdown — bold, code, and bullet lists — plus a typing indicator while a reply streams.
Attach files from the composer or drag them onto the panel — images preview as thumbnails, everything else as type-colored chips, removable before you send.
Replies can produce artifact cards, and the header, footer and cards all link to the full AI Chat page — carrying the same thread, not starting over.
Anatomy
Panel 384×560 docked 22px off the corner; FAB 54px. Every color is a theme token, so it tracks light, dark, and all six presets.
54px circle in --pri with the sparkle mark, a pulsing ring, and an optional unread badge on the corner.
The sparkle mark, “Orbit AI” with a status subline that reads “Thinking…” while replying, and new-chat / open-in-Assistant / collapse buttons.
User bubbles on the right; assistant replies beside a small avatar, rendered with the Markdown editor’s renderMarkdown().
A full-width link row — doc icon, title, and “Open in Assistant” — that carries the reply’s document to the full page.
An auto-grow textarea over an attach button, an “All chats” link, and a send button that only lights up when there’s something to send.
Variants
position picks the corner (22px inset); mode switches between fixed (pinned to the viewport, for the real mount) and inline (absolute within a positioned ancestor, used by these demos). Watch the corner for collisions with a Bottom Dock or Task Tray and flip to bottom-left when needed.
States
Collapsed to the FAB with an unread badge, and the empty state — a greeting with suggestion buttons that prefill the composer. Sending shows a typing indicator until the reply arrives.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "bottom-right" | "bottom-left" | "bottom-right" | Which corner it docks to, with a 22px inset. |
| mode | "fixed" | "inline" | "fixed" | fixed pins it to the viewport (z-index 900) — mount once at the app root. inline positions it absolutely within the nearest positioned ancestor. |
| open | boolean | — | Controlled open state. Pair with onOpenChange. |
| defaultOpen | boolean | true | Start expanded, or collapsed to just the FAB, when uncontrolled. |
| onOpenChange | (open: boolean) => void | — | Called when the panel opens or collapses. |
| messages | ChatWidgetMessage[] | — | Controlled thread. Pair with onMessagesChange. |
| defaultMessages | ChatWidgetMessage[] | [] | Initial thread when uncontrolled. |
| onMessagesChange | (messages: ChatWidgetMessage[]) => void | — | Called with the next thread after a send or the demo reply. |
| artifacts | Record<string, ChatWidgetArtifact> | — | Artifacts keyed by ChatWidgetMessage.artifactId — renders the open-in-Assistant card. |
| unreadCount | number | 1 | FAB badge count (0–9); cleared when the panel opens. |
| assistantHref | string | "/components/ai-chat" | Destination for Open in Assistant, All chats, and artifact cards. |
| greeting | string | "How can I help?" | Empty-state headline. |
| suggestions | string[] | 3 defaults | Empty-state buttons that prefill the composer. |
| placeholder | string | "Ask Orbit AI…" | Composer placeholder. |
| allowAttachments | boolean | true | Show the paperclip and enable drag-and-drop attachments. |
| onSend | (text, attachments) => void | — | Fires on send, before any demo reply — wire it to a real completion. |
| demoReplies | boolean | false | Built-in canned responder + artifact detection for the showcase. Leave off and drive messages from your backend to ship. |
| persistKey | string | — | localStorage key that persists the collapsed/expanded state across pages. |
| …rest | HTMLAttributes<HTMLDivElement> | — | Forwarded to the root wrapper, including ref. |
Usage
Mount it once at the app root
Render it outside the router outlet so the FAB and thread persist across navigation — the point of an always-available assistant.
Trap focus or add a scrim
It’s a non-modal companion. The page stays scrollable and clickable while it’s open; Escape is the way out, not a backdrop.
Hand off with the thread intact
Point assistantHref at your full AI Chat page and share the messages state, so “Open in Assistant” continues the conversation.
Ship the demo replies
demoReplies is a showcase stand-in. Wire onSend to a model and drive messages from your store for real answers.
Keyboard & accessibility
The FAB is labelled “Ask Orbit AI”; the panel is a non-modal role=dialog. Focus moves to the composer on open and returns to the FAB on close — focus is never trapped, and there is no scrim. Incoming replies announce through an aria-live region, every icon button carries a title and aria-label, and the FAB pulse plus the panel’s scale-in are dropped under prefers-reduced-motion.
Its companion is the full AI Chat page — the handoff target where conversations and artifacts live.