Skip to content
UI

Search docs

Search components and documentation

New
← Back to application blocks

chat-01

A chat card with a contact header, a scrollable message thread with user/agent bubbles, and a working message input. Use for support chat or messaging UIs.

Code

"use client"

import * as React from "react"
import { SendIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"

type Message = {
  role: "agent" | "user"
  content: string
}

const initialMessages: Message[] = [
  { role: "agent", content: "Hi, how can I help you today?" },
  { role: "user", content: "Hey, I'm having trouble with my account." },
  { role: "agent", content: "What seems to be the problem?" },
  { role: "user", content: "I can't log in." },
]

export function Chat({ className, ...props }: React.ComponentProps<"div">) {
  const [messages, setMessages] = React.useState<Message[]>(initialMessages)
  const [input, setInput] = React.useState("")
  const endRef = React.useRef<HTMLDivElement>(null)

  React.useEffect(() => {
    endRef.current?.scrollIntoView({ block: "nearest" })
  }, [messages])

  function send(event: React.FormEvent) {
    event.preventDefault()
    const content = input.trim()
    if (!content) return
    setMessages((prev) => [...prev, { role: "user", content }])
    setInput("")
  }

  return (
    <div
      className={cn(
        "mx-auto flex w-full max-w-lg flex-col px-6 py-16",
        className
      )}
      {...props}
    >
      <Card className="flex h-[560px] flex-col">
        <CardHeader className="flex flex-row items-center gap-3 space-y-0 border-b">
          <Avatar>
            <AvatarImage src="https://github.com/shadcn.png" alt="Support" />
            <AvatarFallback>SD</AvatarFallback>
          </Avatar>
          <div>
            <CardTitle className="text-sm">Sofia Davis</CardTitle>
            <CardDescription>support@example.com</CardDescription>
          </div>
        </CardHeader>
        <CardContent className="flex-1 space-y-4 overflow-y-auto py-4">
          {messages.map((message, index) => (
            <div
              key={index}
              className={cn(
                "flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm",
                message.role === "user"
                  ? "ml-auto bg-primary text-primary-foreground"
                  : "bg-muted"
              )}
            >
              {message.content}
            </div>
          ))}
          <div ref={endRef} />
        </CardContent>
        <CardFooter className="border-t pt-6">
          <form onSubmit={send} className="flex w-full items-center gap-2">
            <Input
              value={input}
              onChange={(event) => setInput(event.target.value)}
              placeholder="Type your message..."
              aria-label="Message"
            />
            <Button type="submit" size="icon" disabled={!input.trim()}>
              <SendIcon className="size-4" />
              <span className="sr-only">Send</span>
            </Button>
          </form>
        </CardFooter>
      </Card>
    </div>
  )
}