Chatbot

Queue

A comprehensive queue component system for displaying message lists, todos, and collapsible task sections in AI applications.

The Queue component provides a flexible system for displaying lists of messages, todos, attachments, and collapsible sections. Perfect for showing AI workflow progress, pending tasks, message history, or any structured list of items in your application.

  • How do I set up the project?
  • What is the roadmap for Q4?
  • Can you review my PR?
  • Please generate a changelog.
  • Add dark mode support.
  • Optimize database queries.
  • Set up CI/CD pipeline.
  • Write project documentation
    Complete the README and API docs
  • Implement authentication
  • Fix bug #42
    Resolve crash on settings page
  • Refactor queue logic
    Unify queue and todo state management
  • Add unit tests
    Increase test coverage for hooks
"use client";import {  Queue,  QueueItem,  QueueItemAction,  QueueItemActions,  QueueItemAttachment,  QueueItemContent,  QueueItemDescription,  QueueItemFile,  QueueItemImage,  QueueItemIndicator,  QueueList,  type QueueMessage,  QueueSection,  QueueSectionContent,  QueueSectionLabel,  QueueSectionTrigger,  type QueueTodo,} from "@/components/ai-elements/elements/queue";import { ArrowUp, Trash2 } from "lucide-react";import { useState } from "react";const sampleMessages: QueueMessage[] = [  {    id: "msg-1",    parts: [{ type: "text", text: "How do I set up the project?" }],  },  {    id: "msg-2",    parts: [{ type: "text", text: "What is the roadmap for Q4?" }],  },  {    id: "msg-3",    parts: [{ type: "text", text: "Can you review my PR?" }],  },  {    id: "msg-4",    parts: [{ type: "text", text: "Please generate a changelog." }],  },  {    id: "msg-5",    parts: [{ type: "text", text: "Add dark mode support." }],  },  {    id: "msg-6",    parts: [{ type: "text", text: "Optimize database queries." }],  },  {    id: "msg-7",    parts: [{ type: "text", text: "Set up CI/CD pipeline." }],  },];const sampleTodos: QueueTodo[] = [  {    id: "todo-1",    title: "Write project documentation",    description: "Complete the README and API docs",    status: "completed",  },  {    id: "todo-2",    title: "Implement authentication",    status: "pending",  },  {    id: "todo-3",    title: "Fix bug #42",    description: "Resolve crash on settings page",    status: "pending",  },  {    id: "todo-4",    title: "Refactor queue logic",    description: "Unify queue and todo state management",    status: "pending",  },  {    id: "todo-5",    title: "Add unit tests",    description: "Increase test coverage for hooks",    status: "pending",  },];const Example = () => {  const [messages, setMessages] = useState(sampleMessages);  const [todos, setTodos] = useState(sampleTodos);  const handleRemoveMessage = (id: string) => {    setMessages((prev) => prev.filter((msg) => msg.id !== id));  };  const handleRemoveTodo = (id: string) => {    setTodos((prev) => prev.filter((todo) => todo.id !== id));  };  const handleSendNow = (id: string) => {    console.log("Send now:", id);    handleRemoveMessage(id);  };  if (messages.length === 0 && todos.length === 0) {    return null;  }  return (    <Queue>      {messages.length > 0 && (        <QueueSection>          <QueueSectionTrigger>            <QueueSectionLabel count={messages.length} label="Queued" />          </QueueSectionTrigger>          <QueueSectionContent>            <QueueList>              {messages.map((message) => {                const summary = (() => {                  const textParts = message.parts.filter(                    (p) => p.type === "text"                  );                  const text = textParts                    .map((p) => p.text)                    .join(" ")                    .trim();                  return text || "(queued message)";                })();                const hasFiles = message.parts.some(                  (p) => p.type === "file" && p.url                );                return (                  <QueueItem key={message.id}>                    <div className="flex items-center gap-2">                      <QueueItemIndicator />                      <QueueItemContent>{summary}</QueueItemContent>                      <QueueItemActions>                        <QueueItemAction                          aria-label="Remove from queue"                          onClick={(e) => {                            e.preventDefault();                            e.stopPropagation();                            handleRemoveMessage(message.id);                          }}                          title="Remove from queue"                        >                          <Trash2 size={12} />                        </QueueItemAction>                        <QueueItemAction                          aria-label="Send now"                          onClick={(e) => {                            e.preventDefault();                            e.stopPropagation();                            handleSendNow(message.id);                          }}                        >                          <ArrowUp size={14} />                        </QueueItemAction>                      </QueueItemActions>                    </div>                    {hasFiles && (                      <QueueItemAttachment>                        {message.parts                          .filter((p) => p.type === "file" && p.url)                          .map((file) => {                            if (                              file.mediaType?.startsWith("image/") &&                              file.url                            ) {                              return (                                <QueueItemImage                                  alt={file.filename || "attachment"}                                  key={file.url}                                  src={file.url}                                />                              );                            }                            return (                              <QueueItemFile key={file.url}>                                {file.filename || "file"}                              </QueueItemFile>                            );                          })}                      </QueueItemAttachment>                    )}                  </QueueItem>                );              })}            </QueueList>          </QueueSectionContent>        </QueueSection>      )}      {todos.length > 0 && (        <QueueSection>          <QueueSectionTrigger>            <QueueSectionLabel count={todos.length} label="Todo" />          </QueueSectionTrigger>          <QueueSectionContent>            <QueueList>              {todos.map((todo) => {                const isCompleted = todo.status === "completed";                return (                  <QueueItem key={todo.id}>                    <div className="flex items-center gap-2">                      <QueueItemIndicator completed={isCompleted} />                      <QueueItemContent completed={isCompleted}>                        {todo.title}                      </QueueItemContent>                      <QueueItemActions>                        <QueueItemAction                          aria-label="Remove todo"                          onClick={() => handleRemoveTodo(todo.id)}                        >                          <Trash2 size={12} />                        </QueueItemAction>                      </QueueItemActions>                    </div>                    {todo.description && (                      <QueueItemDescription completed={isCompleted}>                        {todo.description}                      </QueueItemDescription>                    )}                  </QueueItem>                );              })}            </QueueList>          </QueueSectionContent>        </QueueSection>      )}    </Queue>  );};export default Example;

Installation

npx ai-elements@latest add queue
npx shadcn@latest add @ai-elements/queue
"use client";import { Button } from "@repo/shadcn-ui/components/ui/button";import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@repo/shadcn-ui/components/ui/collapsible";import { ScrollArea } from "@repo/shadcn-ui/components/ui/scroll-area";import { cn } from "@repo/shadcn-ui/lib/utils";import { ChevronDownIcon, PaperclipIcon } from "lucide-react";import type { ComponentProps } from "react";export type QueueMessagePart = {  type: string;  text?: string;  url?: string;  filename?: string;  mediaType?: string;};export type QueueMessage = {  id: string;  parts: QueueMessagePart[];};export type QueueTodo = {  id: string;  title: string;  description?: string;  status?: "pending" | "completed";};export type QueueItemProps = ComponentProps<"li">;export const QueueItem = ({ className, ...props }: QueueItemProps) => (  <li    className={cn(      "group flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors hover:bg-muted",      className    )}    {...props}  />);export type QueueItemIndicatorProps = ComponentProps<"span"> & {  completed?: boolean;};export const QueueItemIndicator = ({  completed = false,  className,  ...props}: QueueItemIndicatorProps) => (  <span    className={cn(      "mt-0.5 inline-block size-2.5 rounded-full border",      completed        ? "border-muted-foreground/20 bg-muted-foreground/10"        : "border-muted-foreground/50",      className    )}    {...props}  />);export type QueueItemContentProps = ComponentProps<"span"> & {  completed?: boolean;};export const QueueItemContent = ({  completed = false,  className,  ...props}: QueueItemContentProps) => (  <span    className={cn(      "line-clamp-1 grow break-words",      completed        ? "text-muted-foreground/50 line-through"        : "text-muted-foreground",      className    )}    {...props}  />);export type QueueItemDescriptionProps = ComponentProps<"div"> & {  completed?: boolean;};export const QueueItemDescription = ({  completed = false,  className,  ...props}: QueueItemDescriptionProps) => (  <div    className={cn(      "ml-6 text-xs",      completed        ? "text-muted-foreground/40 line-through"        : "text-muted-foreground",      className    )}    {...props}  />);export type QueueItemActionsProps = ComponentProps<"div">;export const QueueItemActions = ({  className,  ...props}: QueueItemActionsProps) => (  <div className={cn("flex gap-1", className)} {...props} />);export type QueueItemActionProps = Omit<  ComponentProps<typeof Button>,  "variant" | "size">;export const QueueItemAction = ({  className,  ...props}: QueueItemActionProps) => (  <Button    className={cn(      "size-auto rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-muted-foreground/10 hover:text-foreground group-hover:opacity-100",      className    )}    size="icon"    type="button"    variant="ghost"    {...props}  />);export type QueueItemAttachmentProps = ComponentProps<"div">;export const QueueItemAttachment = ({  className,  ...props}: QueueItemAttachmentProps) => (  <div className={cn("mt-1 flex flex-wrap gap-2", className)} {...props} />);export type QueueItemImageProps = ComponentProps<"img">;export const QueueItemImage = ({  className,  ...props}: QueueItemImageProps) => (  <img    alt=""    className={cn("h-8 w-8 rounded border object-cover", className)}    height={32}    width={32}    {...props}  />);export type QueueItemFileProps = ComponentProps<"span">;export const QueueItemFile = ({  children,  className,  ...props}: QueueItemFileProps) => (  <span    className={cn(      "flex items-center gap-1 rounded border bg-muted px-2 py-1 text-xs",      className    )}    {...props}  >    <PaperclipIcon size={12} />    <span className="max-w-[100px] truncate">{children}</span>  </span>);export type QueueListProps = ComponentProps<typeof ScrollArea>;export const QueueList = ({  children,  className,  ...props}: QueueListProps) => (  <ScrollArea className={cn("-mb-1 mt-2", className)} {...props}>    <div className="max-h-40 pr-4">      <ul>{children}</ul>    </div>  </ScrollArea>);// QueueSection - collapsible section containerexport type QueueSectionProps = ComponentProps<typeof Collapsible>;export const QueueSection = ({  className,  defaultOpen = true,  ...props}: QueueSectionProps) => (  <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />);// QueueSectionTrigger - section header/triggerexport type QueueSectionTriggerProps = ComponentProps<"button">;export const QueueSectionTrigger = ({  children,  className,  ...props}: QueueSectionTriggerProps) => (  <CollapsibleTrigger asChild>    <button      className={cn(        "group flex w-full items-center justify-between rounded-md bg-muted/40 px-3 py-2 text-left font-medium text-muted-foreground text-sm transition-colors hover:bg-muted",        className      )}      type="button"      {...props}    >      {children}    </button>  </CollapsibleTrigger>);// QueueSectionLabel - label content with icon and countexport type QueueSectionLabelProps = ComponentProps<"span"> & {  count?: number;  label: string;  icon?: React.ReactNode;};export const QueueSectionLabel = ({  count,  label,  icon,  className,  ...props}: QueueSectionLabelProps) => (  <span className={cn("flex items-center gap-2", className)} {...props}>    <ChevronDownIcon className="group-data-[state=closed]:-rotate-90 size-4 transition-transform" />    {icon}    <span>      {count} {label}    </span>  </span>);// QueueSectionContent - collapsible content areaexport type QueueSectionContentProps = ComponentProps<  typeof CollapsibleContent>;export const QueueSectionContent = ({  className,  ...props}: QueueSectionContentProps) => (  <CollapsibleContent className={cn(className)} {...props} />);export type QueueProps = ComponentProps<"div">;export const Queue = ({ className, ...props }: QueueProps) => (  <div    className={cn(      "flex flex-col gap-2 rounded-xl border border-border bg-background px-3 pt-2 pb-2 shadow-xs",      className    )}    {...props}  />);

Usage

import {
  Queue,
  QueueSection,
  QueueSectionTrigger,
  QueueSectionLabel,
  QueueSectionContent,
  QueueList,
  QueueItem,
  QueueItemIndicator,
  QueueItemContent,
} from '@/components/ai-elements/queue';
<Queue>
  <QueueSection>
    <QueueSectionTrigger>
      <QueueSectionLabel count={3} label="Tasks" />
    </QueueSectionTrigger>
    <QueueSectionContent>
      <QueueList>
        <QueueItem>
          <QueueItemIndicator />
          <QueueItemContent>Analyze user requirements</QueueItemContent>
        </QueueItem>
      </QueueList>
    </QueueSectionContent>
  </QueueSection>
</Queue>

Features

  • Flexible component system with composable parts
  • Collapsible sections with smooth animations
  • Support for completed/pending state indicators
  • Built-in scroll area for long lists
  • Attachment display with images and file indicators
  • Hover-revealed action buttons for queue items
  • TypeScript support with comprehensive type definitions
  • Customizable styling with Tailwind CSS
  • Responsive design with mobile-friendly interactions
  • Keyboard navigation and accessibility support
  • Theme-aware with automatic dark mode support

Examples

With PromptInput

  • Write project documentation
    Complete the README and API docs
  • Implement authentication
  • Fix bug #42
    Resolve crash on settings page
  • Refactor queue logic
    Unify queue and todo state management
  • Add unit tests
    Increase test coverage for hooks
  • "use client";import {  PromptInput,  PromptInputActionAddAttachments,  PromptInputActionMenu,  PromptInputActionMenuContent,  PromptInputActionMenuTrigger,  PromptInputAttachment,  PromptInputAttachments,  PromptInputBody,  PromptInputButton,  PromptInputFooter,  type PromptInputMessage,  PromptInputModelSelect,  PromptInputModelSelectContent,  PromptInputModelSelectItem,  PromptInputModelSelectTrigger,  PromptInputModelSelectValue,  PromptInputSpeechButton,  PromptInputSubmit,  PromptInputTextarea,  PromptInputTools,} from "@/components/ai-elements/elements/prompt-input";import {  Queue,  QueueItem,  QueueItemAction,  QueueItemActions,  QueueItemContent,  QueueItemDescription,  QueueItemIndicator,  QueueSection,  QueueSectionContent,  type QueueTodo,} from "@/components/ai-elements/elements/queue";import { GlobeIcon, Trash2 } from "lucide-react";import { useRef, useState } from "react";const models = [  { id: "gpt-4", name: "GPT-4" },  { id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo" },  { id: "claude-2", name: "Claude 2" },  { id: "claude-instant", name: "Claude Instant" },  { id: "palm-2", name: "PaLM 2" },  { id: "llama-2-70b", name: "Llama 2 70B" },  { id: "llama-2-13b", name: "Llama 2 13B" },  { id: "cohere-command", name: "Command" },  { id: "mistral-7b", name: "Mistral 7B" },];const SUBMITTING_TIMEOUT = 200;const STREAMING_TIMEOUT = 2000;const sampleTodos: QueueTodo[] = [  {    id: "todo-1",    title: "Write project documentation",    description: "Complete the README and API docs",    status: "completed",  },  {    id: "todo-2",    title: "Implement authentication",    status: "pending",  },  {    id: "todo-3",    title: "Fix bug #42",    description: "Resolve crash on settings page",    status: "pending",  },  {    id: "todo-4",    title: "Refactor queue logic",    description: "Unify queue and todo state management",    status: "pending",  },  {    id: "todo-5",    title: "Add unit tests",    description: "Increase test coverage for hooks",    status: "pending",  },];const Example = () => {  const [todos, setTodos] = useState(sampleTodos);  const handleRemoveTodo = (id: string) => {    setTodos((prev) => prev.filter((todo) => todo.id !== id));  };  const [text, setText] = useState<string>("");  const [model, setModel] = useState<string>(models[0].id);  const [status, setStatus] = useState<    "submitted" | "streaming" | "ready" | "error"  >("ready");  const timeoutRef = useRef<NodeJS.Timeout | null>(null);  const textareaRef = useRef<HTMLTextAreaElement>(null);  const stop = () => {    console.log("Stopping request...");    // Clear any pending timeouts    if (timeoutRef.current) {      clearTimeout(timeoutRef.current);      timeoutRef.current = null;    }    setStatus("ready");  };  const handleSubmit = (message: PromptInputMessage) => {    // If currently streaming or submitted, stop instead of submitting    if (status === "streaming" || status === "submitted") {      stop();      return;    }    const hasText = Boolean(message.text);    const hasAttachments = Boolean(message.files?.length);    if (!(hasText || hasAttachments)) {      return;    }    setStatus("submitted");    console.log("Submitting message:", message);    setTimeout(() => {      setStatus("streaming");    }, SUBMITTING_TIMEOUT);    timeoutRef.current = setTimeout(() => {      setStatus("ready");      timeoutRef.current = null;    }, STREAMING_TIMEOUT);  };  return (    <div className="flex size-full flex-col justify-end">      <Queue className="mx-auto max-h-[150px] w-[95%] overflow-y-auto rounded-b-none border-input border-b-0">        {todos.length > 0 && (          <QueueSection>            <QueueSectionContent>              <div>                {todos.map((todo) => {                  const isCompleted = todo.status === "completed";                  return (                    <QueueItem key={todo.id}>                      <div className="flex items-center gap-2">                        <QueueItemIndicator completed={isCompleted} />                        <QueueItemContent completed={isCompleted}>                          {todo.title}                        </QueueItemContent>                        <QueueItemActions>                          <QueueItemAction                            aria-label="Remove todo"                            onClick={() => handleRemoveTodo(todo.id)}                          >                            <Trash2 size={12} />                          </QueueItemAction>                        </QueueItemActions>                      </div>                      {todo.description && (                        <QueueItemDescription completed={isCompleted}>                          {todo.description}                        </QueueItemDescription>                      )}                    </QueueItem>                  );                })}              </div>            </QueueSectionContent>          </QueueSection>        )}      </Queue>      <PromptInput globalDrop multiple onSubmit={handleSubmit}>        <PromptInputBody>          <PromptInputAttachments>            {(attachment) => <PromptInputAttachment data={attachment} />}          </PromptInputAttachments>          <PromptInputTextarea            onChange={(e) => setText(e.target.value)}            ref={textareaRef}            value={text}          />        </PromptInputBody>        <PromptInputFooter>          <PromptInputTools>            <PromptInputActionMenu>              <PromptInputActionMenuTrigger />              <PromptInputActionMenuContent>                <PromptInputActionAddAttachments />              </PromptInputActionMenuContent>            </PromptInputActionMenu>            <PromptInputSpeechButton              onTranscriptionChange={setText}              textareaRef={textareaRef}            />            <PromptInputButton>              <GlobeIcon size={16} />              <span>Search</span>            </PromptInputButton>            <PromptInputModelSelect onValueChange={setModel} value={model}>              <PromptInputModelSelectTrigger>                <PromptInputModelSelectValue />              </PromptInputModelSelectTrigger>              <PromptInputModelSelectContent>                {models.map((modelOption) => (                  <PromptInputModelSelectItem                    key={modelOption.id}                    value={modelOption.id}                  >                    {modelOption.name}                  </PromptInputModelSelectItem>                ))}              </PromptInputModelSelectContent>            </PromptInputModelSelect>          </PromptInputTools>          <PromptInputSubmit status={status} />        </PromptInputFooter>      </PromptInput>    </div>  );};export default Example;

    Props

    <Queue />

    Prop

    Type

    <QueueSection />

    Prop

    Type

    <QueueSectionTrigger />

    Prop

    Type

    <QueueSectionLabel />

    Prop

    Type

    <QueueSectionContent />

    Prop

    Type

    <QueueList />

    Prop

    Type

    <QueueItem />

    Prop

    Type

    <QueueItemIndicator />

    Prop

    Type

    <QueueItemContent />

    Prop

    Type

    <QueueItemDescription />

    Prop

    Type

    <QueueItemActions />

    Prop

    Type

    <QueueItemAction />

    Prop

    Type

    <QueueItemAttachment />

    Prop

    Type

    <QueueItemImage />

    Prop

    Type

    <QueueItemFile />

    Prop

    Type