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
"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 tasknpx 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:
'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:
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