Elements

Elements · AUI connected · AUI

Assistant modal

A floating chat bubble for support widgets, help desks, and embedded assistants.

fig. 01

Installation

npx shadcn@latest add "@assistant-ui/assistant-modal"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Assistant modal tucks a complete Thread behind a floating corner trigger: a bot icon that becomes a chevron once open, above a popover that holds the conversation. It has no standalone form, since it is a fixed composition of Thread and a runtime-driven open state rather than a set of props.

Getting started

AssistantModal needs nothing beyond a runtime provider higher up the tree; it positions and opens itself.

Drop it in

app/layout.tsx
import { AssistantModal } from "@/components/assistant-ui/elements/assistant-modal.aui";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <AssistantModal />
    </>
  );
}

Needs an AssistantRuntimeProvider ancestor. Render it once, anywhere under the provider, since it is fixed-positioned to the viewport corner on its own.

Anatomy

<div className="aui-modal-anchor fixed end-4 bottom-4 size-11">
  <button aria-label="Open Assistant">{/* bot icon, crossfades to a chevron once open */}</button>
</div>

<div className="aui-modal-content"> {/* opens above the trigger */}
  <Thread />
</div>

The modal opens itself on the runtime's thread.runStart event, so sending the first message expands it even if it was launched collapsed. Once open, clicking outside or losing focus does not close it; only clicking the trigger again does, since the popover cancels every outside-press and focus-out close and honors just the explicit toggle. The trigger's accessible label switches between "Open Assistant" and "Close Assistant" along with the icon crossfade.

Examples

Trigger it from elsewhere

AssistantModal owns its open state internally and exposes no open prop or ref. To open it from a different control, such as a header button, lift the open/setOpen pair already at the top of the component into your own copy of the file.

Restyle the trigger and panel

The anchor, panel, and button each carry a stable class name you can target from outside without touching the copied file: aui-modal-anchor positions the trigger, aui-modal-content sizes and animates the panel, and aui-modal-button styles the trigger itself.

.aui-modal-content {
  width: 28rem;
  border-radius: 1rem;
}

API reference

AssistantModal

Takes no props.

Behavior

EventFiresEffect
thread.runStartOnce per run start, any threadOpens the modal via aui.on("thread.runStart", ...).

Class names

ClassTargets
aui-modal-anchorThe fixed-position trigger wrapper.
aui-modal-contentThe popover panel that holds Thread.
aui-modal-buttonThe floating trigger button.