Chatbot

Task

A collapsible task list component for displaying AI workflow progress, with status indicators and optional descriptions.

The Task component provides a structured way to display task lists or workflow progress with collapsible details, status indicators, and progress tracking. It consists of a main Task container with TaskTrigger for the clickable header and TaskContent for the collapsible content area.

Found project files

Searching "app/page.tsx, components structure"
Read
Reactpage.tsx
Scanning 52 files
Scanning 2 files
Reading files
Reactlayout.tsx
"use client";import { SiReact } from "@icons-pack/react-simple-icons";import {  Task,  TaskContent,  TaskItem,  TaskItemFile,  TaskTrigger,} from "@/components/ai-elements/elements/task";import { nanoid } from "nanoid";import type { ReactNode } from "react";const Example = () => {  const tasks: { key: string; value: ReactNode }[] = [    { key: nanoid(), value: 'Searching "app/page.tsx, components structure"' },    {      key: nanoid(),      value: (        <span className="inline-flex items-center gap-1" key="read-page-tsx">          Read          <TaskItemFile>            <SiReact className="size-4" color="#149ECA" />            <span>page.tsx</span>          </TaskItemFile>        </span>      ),    },    { key: nanoid(), value: "Scanning 52 files" },    { key: nanoid(), value: "Scanning 2 files" },    {      key: nanoid(),      value: (        <span className="inline-flex items-center gap-1" key="read-layout-tsx">          Reading files          <TaskItemFile>            <SiReact className="size-4" color="#149ECA" />            <span>layout.tsx</span>          </TaskItemFile>        </span>      ),    },  ];  return (    <div style={{ height: "200px" }}>      <Task className="w-full">        <TaskTrigger title="Found project files" />        <TaskContent>          {tasks.map((task) => (            <TaskItem key={task.key}>{task.value}</TaskItem>          ))}        </TaskContent>      </Task>    </div>  );};export default Example;

Installation

npx ai-elements@latest add task
npx shadcn@latest add @ai-elements/task
"use client";import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@repo/shadcn-ui/components/ui/collapsible";import { cn } from "@repo/shadcn-ui/lib/utils";import { ChevronDownIcon, SearchIcon } from "lucide-react";import type { ComponentProps } from "react";export type TaskItemFileProps = ComponentProps<"div">;export const TaskItemFile = ({  children,  className,  ...props}: TaskItemFileProps) => (  <div    className={cn(      "inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-foreground text-xs",      className    )}    {...props}  >    {children}  </div>);export type TaskItemProps = ComponentProps<"div">;export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (  <div className={cn("text-muted-foreground text-sm", className)} {...props}>    {children}  </div>);export type TaskProps = ComponentProps<typeof Collapsible>;export const Task = ({  defaultOpen = true,  className,  ...props}: TaskProps) => (  <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />);export type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {  title: string;};export const TaskTrigger = ({  children,  className,  title,  ...props}: TaskTriggerProps) => (  <CollapsibleTrigger asChild className={cn("group", className)} {...props}>    {children ?? (      <div className="flex w-full cursor-pointer items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground">        <SearchIcon className="size-4" />        <p className="text-sm">{title}</p>        <ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />      </div>    )}  </CollapsibleTrigger>);export type TaskContentProps = ComponentProps<typeof CollapsibleContent>;export const TaskContent = ({  children,  className,  ...props}: TaskContentProps) => (  <CollapsibleContent    className={cn(      "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",      className    )}    {...props}  >    <div className="mt-4 space-y-2 border-muted border-l-2 pl-4">      {children}    </div>  </CollapsibleContent>);

Usage

import {
  Task,
  TaskContent,
  TaskItem,
  TaskItemFile,
  TaskTrigger,
} from '@/components/ai-elements/task';
<Task className="w-full">
  <TaskTrigger title="Found project files" />
  <TaskContent>
    <TaskItem>
      Read <TaskItemFile>index.md</TaskItemFile>
    </TaskItem>
  </TaskContent>
</Task>

Usage with AI SDK

Build a mock async programming agent using experimental_generateObject.

Add the following component to your frontend:

app/page.tsx
'use client';

import { experimental_useObject as useObject } from '@ai-sdk/react';
import {
  Task,
  TaskItem,
  TaskItemFile,
  TaskTrigger,
  TaskContent,
} from '@/components/ai-elements/task';
import { Button } from '@/components/ui/button';
import { tasksSchema } from '@/app/api/task/route';
import {
  SiReact,
  SiTypescript,
  SiJavascript,
  SiCss,
  SiHtml5,
  SiJson,
  SiMarkdown,
} from '@icons-pack/react-simple-icons';

const iconMap = {
  react: { component: SiReact, color: '#149ECA' },
  typescript: { component: SiTypescript, color: '#3178C6' },
  javascript: { component: SiJavascript, color: '#F7DF1E' },
  css: { component: SiCss, color: '#1572B6' },
  html: { component: SiHtml5, color: '#E34F26' },
  json: { component: SiJson, color: '#000000' },
  markdown: { component: SiMarkdown, color: '#000000' },
};

const TaskDemo = () => {
  const { object, submit, isLoading } = useObject({
    api: '/api/agent',
    schema: tasksSchema,
  });

  const handleSubmit = (taskType: string) => {
    submit({ prompt: taskType });
  };

  const renderTaskItem = (item: any, index: number) => {
    if (item?.type === 'file' && item.file) {
      const iconInfo = iconMap[item.file.icon as keyof typeof iconMap];
      if (iconInfo) {
        const IconComponent = iconInfo.component;
        return (
          <span className="inline-flex items-center gap-1" key={index}>
            {item.text}
            <TaskItemFile>
              <IconComponent
                color={item.file.color || iconInfo.color}
                className="size-4"
              />
              <span>{item.file.name}</span>
            </TaskItemFile>
          </span>
        );
      }
    }
    return item?.text || '';
  };

  return (
    <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
      <div className="flex flex-col h-full">
        <div className="flex gap-2 mb-6 flex-wrap">
          <Button
            onClick={() => handleSubmit('React component development')}
            disabled={isLoading}
            variant="outline"
          >
            React Development
          </Button>
        </div>

        <div className="flex-1 overflow-auto space-y-4">
          {isLoading && !object && (
            <div className="text-muted-foreground">Generating tasks...</div>
          )}

          {object?.tasks?.map((task: any, taskIndex: number) => (
            <Task key={taskIndex} defaultOpen={taskIndex === 0}>
              <TaskTrigger title={task.title || 'Loading...'} />
              <TaskContent>
                {task.items?.map((item: any, itemIndex: number) => (
                  <TaskItem key={itemIndex}>
                    {renderTaskItem(item, itemIndex)}
                  </TaskItem>
                ))}
              </TaskContent>
            </Task>
          ))}
        </div>
      </div>
    </div>
  );
};

export default TaskDemo;

Add the following route to your backend:

app/api/agent.ts
import { streamObject } from 'ai';
import { z } from 'zod';

export const taskItemSchema = z.object({
  type: z.enum(['text', 'file']),
  text: z.string(),
  file: z
    .object({
      name: z.string(),
      icon: z.string(),
      color: z.string().optional(),
    })
    .optional(),
});

export const taskSchema = z.object({
  title: z.string(),
  items: z.array(taskItemSchema),
  status: z.enum(['pending', 'in_progress', 'completed']),
});

export const tasksSchema = z.object({
  tasks: z.array(taskSchema),
});

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = streamObject({
    model: 'openai/gpt-4o',
    schema: tasksSchema,
    prompt: `You are an AI assistant that generates realistic development task workflows. Generate a set of tasks that would occur during ${prompt}.

    Each task should have:
    - A descriptive title
    - Multiple task items showing the progression
    - Some items should be plain text, others should reference files
    - Use realistic file names and appropriate file types
    - Status should progress from pending to in_progress to completed

    For file items, use these icon types: 'react', 'typescript', 'javascript', 'css', 'html', 'json', 'markdown'

    Generate 3-4 tasks total, with 4-6 items each.`,
  });

  return result.toTextStreamResponse();
}

Features

  • Visual icons for pending, in-progress, completed, and error states
  • Expandable content for task descriptions and additional information
  • Built-in progress counter showing completed vs total tasks
  • Optional progressive reveal of tasks with customizable timing
  • Support for custom content within task items
  • Full type safety with proper TypeScript definitions
  • Keyboard navigation and screen reader support

Props

<Task />

Prop

Type

<TaskTrigger />

Prop

Type

<TaskContent />

Prop

Type

<TaskItem />

Prop

Type

<TaskItemFile />

Prop

Type