AI Panel
The assistant sidepanel: a header, a scrollable conversation and a pinned composer in one column. Drop a Chat in the body and a Prompt Input in the footer. Pure layout, so it docks in a page or drops into a Drawer.
Installation#
Usage#
import {
AIPanel,
AIPanelHeader,
AIPanelHeading,
AIPanelTitle,
AIPanelBody,
AIPanelFooter,
} from "@/components/ui/ai-panel"
import { Conversation } from "@/components/ui/chat"
import { PromptInput } from "@/components/ui/prompt-input"
export function Assistant() {
return (
<AIPanel>
<AIPanelHeader>
<AIPanelHeading>
<AIPanelTitle>Assistant</AIPanelTitle>
</AIPanelHeading>
</AIPanelHeader>
<AIPanelBody>
<Conversation>{/* messages */}</Conversation>
</AIPanelBody>
<AIPanelFooter>
<PromptInput onSubmit={send}>{/* composer */}</PromptInput>
</AIPanelFooter>
</AIPanel>
)
}The panel fills its container (h-full) and owns the scroll: only the body scrolls, while the header and composer stay pinned. Give it a sized frame when docking it in a page.
Window modes (split & floating)#
The panel can manage its own window, Notion/Edge-style. Beyond the embedded inline default, it has two windowed views: split (a full-height rail pinned to the right edge, the split-screen assistant) and floating (a compact window hovering bottom-right). Drop an AIPanelExpandToggle in the header to flip between them; it swaps its own icon and label with the view. These modes are non-modal: the page stays interactive, so closing is the host's call (render the panel behind a launcher). The view is controllable via view / defaultView / onViewChange.
As an overlay (Drawer)#
Because it's pure layout, the same panel drops straight into a DrawerContent for a slide-in assistant: the Drawer brings the trigger, scrim, focus trap, and animation. Hide the Drawer's own close (showClose=false) and put a close action in the panel header instead. An overlay is never a dead end: keep a minimize to window action that pops the panel out to a floating window (and back), so the assistant can ride along while the user keeps working.
Empty state#
With no messages yet, center a greeting and a few starters in the body. The body is a plain flex region, so a centered welcome composes with no extra parts. For genuine no-content moments (no history, no results, offline), reach for the canonical EmptyState instead: see below.
No content & error states#
Beyond the welcome state, an assistant hits real no-content moments: an empty history, a search that finds nothing, a dropped connection. Because the body is a plain flex region, drop the canonical EmptyState straight in. density="compact" fits the in-panel scale, and variant re-themes the media and action for an error (destructive) versus a neutral zero state (default).
API reference#
AIPanel
The shell: a full-height flex column on the popover surface, exposing --surface so the nested composer blends in. Owns the view state: inline (fills container), split (right rail), or floating, controllable via view / defaultView / onViewChange. Forwards all native div props.
AIPanelHeader · AIPanelHeading · AIPanelTitle · AIPanelDescription
The bordered header row: leading media (e.g. an Avatar), a title/description column, and actions. Inside a Drawer, also render a <DrawerTitle className="sr-only"> for screen readers.
AIPanelActions · AIPanelAction · AIPanelExpandToggle
Header actions: new chat, history, close. AIPanelAction is a ghost icon Button (all Button props apply; use asChild to wrap a DrawerClose). AIPanelExpandToggle is a prewired action that flips the panel between the split rail and the floating window.
AIPanelBody · AIPanelFooter
AIPanelBody is the only scrolling region; drop a Conversation inside. AIPanelFooter is the pinned composer dock; drop a PromptInput inside.