Chat

Chat Dock

Floating chat launcher that opens a docked panel built on Base UI Dialog, pinned to the viewport or a container, with optional scrim and header, body and footer parts.

The source comes with full access.

One payment of $149 ($49 for now) opens the source right here and lets the CLI install every paid component, with lifetime updates.

Installation

pnpm dlx shadcn add @01orbital/chat-dock
npx shadcn add @01orbital/chat-dock
yarn shadcn add @01orbital/chat-dock
bunx --bun shadcn add @01orbital/chat-dock

Part of the 01orbital library. With a license, put ORBITAL_TOKEN in .env.local and the same command installs it (licensed install, pricing).

It lands at components/chat/chat-dock.tsx, as source you own and edit.

The example also uses chat-composer, chat-conversation, chat-message and chat-response. Install them too:

pnpm dlx shadcn add @01orbital/chat-composer @01orbital/chat-conversation @01orbital/chat-message @01orbital/chat-response
npx shadcn add @01orbital/chat-composer @01orbital/chat-conversation @01orbital/chat-message @01orbital/chat-response
yarn shadcn add @01orbital/chat-composer @01orbital/chat-conversation @01orbital/chat-message @01orbital/chat-response
bunx --bun shadcn add @01orbital/chat-composer @01orbital/chat-conversation @01orbital/chat-message @01orbital/chat-response

Usage

"use client"

import * as React from "react"

import { ChatComposer } from "@/components/chat/chat-composer"
import {
  ChatConversation,
  ChatConversationContent,
  ChatConversationTurn,
} from "@/components/chat/chat-conversation"
import {
  ChatDock,
  ChatDockBody,
  ChatDockFooter,
  ChatDockHeader,
  ChatDockTitle,
} from "@/components/chat/chat-dock"
import { ChatMessage } from "@/components/chat/chat-message"
import { ChatResponse } from "@/components/chat/chat-response"

// Every word on the page. Change copy here, not in the markup.
const COPY = {
  title: "Ask Orbcore",
  question: "Can I change the copy and the layout?",
  answer:
    "Every block keeps its words in a constant at the top of the file and its layout in named parts.",
  placeholder: "Ask anything…",
} as const

// The dock, open, anchored to this frame instead of the viewport.
export default function ChatDockDemo() {
  const frameRef = React.useRef<HTMLDivElement>(null)
  const [open, setOpen] = React.useState(true)
  const [sent, setSent] = React.useState<string[]>([])

  return (
    <div ref={frameRef} className="relative h-144 w-full overflow-hidden">
      <ChatDock
        open={open}
        onOpenChange={setOpen}
        anchor="container"
        container={frameRef}
      >
        <ChatDockHeader>
          <ChatDockTitle>{COPY.title}</ChatDockTitle>
        </ChatDockHeader>
        <ChatDockBody className="flex flex-col">
          <ChatConversation className="px-4 py-4">
            <ChatConversationContent className="gap-6">
              <ChatConversationTurn messageId="usr-opening" scrollAnchor>
                <ChatMessage variant="mine">{COPY.question}</ChatMessage>
              </ChatConversationTurn>
              <ChatConversationTurn messageId="asst-opening">
                <ChatResponse
                  className="text-sm/7 text-foreground"
                  text={COPY.answer}
                />
              </ChatConversationTurn>
              {sent.map((line, i) => (
                <ChatConversationTurn
                  key={i}
                  messageId={`sent-${i}`}
                  scrollAnchor
                >
                  <ChatMessage variant="mine">{line}</ChatMessage>
                </ChatConversationTurn>
              ))}
            </ChatConversationContent>
          </ChatConversation>
        </ChatDockBody>
        <ChatDockFooter>
          <ChatComposer
            onSend={(value) => setSent((prev) => [...prev, value])}
            onStop={() => {}}
            streaming={false}
            placeholder={COPY.placeholder}
          />
        </ChatDockFooter>
      </ChatDock>
    </div>
  )
}

Anatomy

Chat Dock is built from 5 parts. In the example they nest like this:

ChatDock
├─ ChatDockHeader
│  └─ ChatDockTitle
├─ ChatDockBody
└─ ChatDockFooter
  • ChatDock: A floating chat: a round launcher in the corner that opens a chat panel above it. Put ChatDockHeader, ChatDockBody and ChatDockFooter inside.
  • ChatDockHeader: The panel's top bar: your title on the left and a close button on the right.
  • ChatDockTitle: The panel's title, which also names the dialog for screen readers.
  • ChatDockBody: The panel's middle, which fills the space between header and footer; put the conversation here.
  • ChatDockFooter: The panel's bottom bar, above a thin rule; put the composer here.

API reference

ChatDock

A floating chat: a round launcher in the corner that opens a chat panel above it. Put ChatDockHeader, ChatDockBody and ChatDockFooter inside.

Prop

Type

ChatDockHeader

The panel's top bar: your title on the left and a close button on the right.

It accepts the HTML attributes of the element it renders.

ChatDockTitle

The panel's title, which also names the dialog for screen readers.

It accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.

ChatDockBody

The panel's middle, which fills the space between header and footer; put the conversation here.

It accepts the HTML attributes of the element it renders.

The panel's bottom bar, above a thin rule; put the composer here.

It accepts the HTML attributes of the element it renders.

Dependencies

npm: @base-ui/react, @phosphor-icons/react, cn. The CLI installs them for you.

Registry pieces it installs with it:

  • button