# Chat With Suggested Prompt Start

> AI chat opening on a suggested-prompt empty state, then switching to a message thread once a prompt is sent, with a multi-line composer.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/ai-chat-2).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/ai-chat-2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/ai-chat-2-light-f411609f8bda4b47.png
- Registry item: https://7ovr.com/r/ai-chat-2.json
- Categories: ai-chat
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/ai-chat-2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `avatar`, `badge`, `bubble`, `button`, `card`, `kbd`, `message`, `message-scroller`, `textarea`

## Files

### `registry/blocks/ai-chat/2/ai-chat-block.tsx`

```tsx
"use client"

import * as React from "react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { Message, MessageAvatar, MessageContent } from "@/components/ui/message"
import {
  MessageScroller,
  MessageScrollerButton,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
} from "@/components/ui/message-scroller"
import { Textarea } from "@/components/ui/textarea"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

type IconProps = { className?: string; size?: number | string }

const suggestions = [
  {
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="ChartBar"
        tabler="IconChartBar"
        hugeicons="BarChartIcon"
        phosphor="ChartBar"
        remixicon="RiBarChartBoxLine"
        {...p}
      />
    ),
    label: "Summarise last quarter's revenue",
  },
  {
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="FileText"
        tabler="IconFileText"
        hugeicons="File01Icon"
        phosphor="FileText"
        remixicon="RiFileTextLine"
        {...p}
      />
    ),
    label: "Draft a status update for stakeholders",
  },
  {
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Lightbulb"
        tabler="IconBulb"
        hugeicons="Idea01Icon"
        phosphor="Lightbulb"
        remixicon="RiLightbulbLine"
        {...p}
      />
    ),
    label: "Suggest three ways to reduce churn",
  },
  {
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Sparkles"
        tabler="IconSparkles"
        hugeicons="SparklesIcon"
        phosphor="Sparkle"
        remixicon="RiSparklingLine"
        {...p}
      />
    ),
    label: "What are my top action items today?",
  },
]

const CANNED_REPLIES = [
  "Here's what I found: the latest figures are trending up across your core segments, with no anomalies worth flagging.",
  "Good ask. I've put together a concise summary you can drop straight into your update: headline metric, main driver, and current risk.",
  "Based on the workspace data, the top three opportunities all tie back to onboarding and activation. Want me to expand on any of them?",
  "Your priorities for today: two items need a decision, one is waiting on review. I can draft the follow-ups if that helps.",
]

type MessageRole = "user" | "assistant"

interface ChatMessage {
  id: number
  role: MessageRole
  content: string
}

export default function AiChatBlock() {
  const [messages, setMessages] = React.useState<ChatMessage[]>([])
  const [draft, setDraft] = React.useState("")
  const nextId = React.useRef(1)
  const replyIdx = React.useRef(0)
  const replyTimers = React.useRef<ReturnType<typeof setTimeout>[]>([])
  const started = messages.length > 0

  React.useEffect(() => {
    const timers = replyTimers.current
    return () => timers.forEach(clearTimeout)
  }, [])

  function sendMessage(text: string) {
    const content = text.trim()
    if (!content) return

    setMessages((prev) => [
      ...prev,
      { id: nextId.current++, role: "user", content },
    ])
    setDraft("")

    const reply = CANNED_REPLIES[replyIdx.current % CANNED_REPLIES.length]
    replyIdx.current += 1
    const timer = setTimeout(() => {
      setMessages((prev) => [
        ...prev,
        { id: nextId.current++, role: "assistant", content: reply },
      ])
    }, 600)
    replyTimers.current.push(timer)
  }

  return (
    <section className="flex w-full items-center justify-center bg-background px-6 py-12 text-foreground">
      <MessageScrollerProvider autoScroll defaultScrollPosition="end">
        <Card className="w-full max-w-lg">
          <CardHeader className="flex flex-col items-center gap-4 pt-8 pb-2">
            <div className="flex size-12 items-center justify-center rounded-lg bg-primary text-primary-foreground">
              <IconPlaceholder
                lucide="Sparkles"
                tabler="IconSparkles"
                hugeicons="SparklesIcon"
                phosphor="Sparkle"
                remixicon="RiSparklingLine"
                className="size-6"
                aria-hidden="true"
              />
            </div>
            <div className="flex flex-col items-center gap-1 text-center">
              <h2 className="font-heading text-base font-semibold tracking-tight">
                Acme AI Assistant
              </h2>
              <p className="max-w-xs text-xs text-muted-foreground">
                Ask me anything about your workspace: reports, drafts, insights,
                or next steps.
              </p>
            </div>
            <Badge variant="secondary" className="gap-1.5">
              <span className="size-1.5 bg-primary" />
              Ready
            </Badge>
          </CardHeader>

          <CardContent className="flex flex-col gap-3 px-5 pb-0">
            {started ? (
              <MessageScroller className="h-64">
                <MessageScrollerViewport>
                  <MessageScrollerContent className="gap-4 pr-2.5">
                    {messages.map((msg) => (
                      <MessageScrollerItem
                        key={msg.id}
                        messageId={String(msg.id)}
                        scrollAnchor={msg.role === "user"}
                      >
                        <Message align={msg.role === "user" ? "end" : "start"}>
                          {msg.role === "assistant" && (
                            <MessageAvatar>
                              <Avatar className="size-7 border border-border">
                                <AvatarFallback className="bg-primary text-primary-foreground">
                                  <IconPlaceholder
                                    lucide="Sparkles"
                                    tabler="IconSparkles"
                                    hugeicons="SparklesIcon"
                                    phosphor="Sparkle"
                                    remixicon="RiSparklingFill"
                                    className="size-4"
                                    aria-hidden="true"
                                  />
                                </AvatarFallback>
                              </Avatar>
                            </MessageAvatar>
                          )}
                          <MessageContent>
                            <Bubble
                              variant={
                                msg.role === "user" ? "default" : "muted"
                              }
                            >
                              <BubbleContent className="whitespace-pre-line">
                                {msg.content}
                              </BubbleContent>
                            </Bubble>
                          </MessageContent>
                        </Message>
                      </MessageScrollerItem>
                    ))}
                  </MessageScrollerContent>
                </MessageScrollerViewport>
                <MessageScrollerButton />
              </MessageScroller>
            ) : (
              <>
                <p className="text-center text-[10px] font-medium tracking-widest text-muted-foreground uppercase">
                  Suggested Prompts
                </p>
                <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
                  {suggestions.map(({ icon: Icon, label }) => (
                    <button
                      key={label}
                      type="button"
                      onClick={() => sendMessage(label)}
                      className="flex cursor-pointer items-start gap-2.5 rounded-lg border border-border bg-muted/50 px-3 py-2.5 text-left transition-colors hover:bg-muted focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
                    >
                      <Icon className="mt-px size-3.5 shrink-0 text-muted-foreground" />
                      <span className="text-xs leading-snug text-foreground">
                        {label}
                      </span>
                    </button>
                  ))}
                </div>
              </>
            )}
          </CardContent>

          <CardFooter className="px-5 pt-4 pb-5">
            <form
              onSubmit={(e) => {
                e.preventDefault()
                sendMessage(draft)
              }}
              className="flex w-full flex-col gap-2"
            >
              <Textarea
                value={draft}
                onChange={(e) => setDraft(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter" && !e.shiftKey) {
                    e.preventDefault()
                    sendMessage(draft)
                  }
                }}
                placeholder="Ask anything…"
                aria-label="Ask anything"
                className="resize-none"
                rows={3}
              />
              <div className="flex w-full items-center justify-between">
                <span className="flex items-center gap-1 text-[10px] text-muted-foreground">
                  <KbdGroup>
                    <Kbd>Shift</Kbd>
                    <Kbd>Enter</Kbd>
                  </KbdGroup>
                  New Line
                </span>
                <Button type="submit" size="sm" disabled={!draft.trim()}>
                  <IconPlaceholder
                    lucide="Send"
                    tabler="IconSend"
                    hugeicons="SentIcon"
                    phosphor="PaperPlane"
                    remixicon="RiSendPlaneLine"
                    data-icon="inline-start"
                  />
                  Send
                </Button>
              </div>
            </form>
          </CardFooter>
        </Card>
      </MessageScrollerProvider>
    </section>
  )
}
```
