Kosmesis
Components

Task

A single collapsible agent task card with a live status pill. Purely presentational — no Morphos equivalent.

Installation

npx kosmesis add task
pnpm dlx kosmesis add task
yarn dlx kosmesis add task
bunx kosmesis add task

Install the following dependencies:

npm install @morphos/icons
pnpm add @morphos/icons
yarn add @morphos/icons
bun add @morphos/icons

Copy and paste the following code into your project.

task.tsx
import { StatefulComponent } from "@praxisjs/core";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export type TaskStatus = "pending" | "running" | "done" | "error";export interface TaskProps {  title: string;  status?: TaskStatus;  defaultOpen?: boolean;  class?: string;  children?: Children;}@Component()export class Task extends StatefulComponent {  @Prop() title = "";  @Prop() status: TaskStatus = "pending";  @Prop() defaultOpen = false;  @Prop() class?: string;  @Prop() children?: Children;  @State() _open = false;  onBeforeMount() {    this._open = this.defaultOpen;  }  toggle(): void {    this._open = !this._open;  }  render() {    return (      <div data-slot="task" data-status={() => this.status} class={cn("rounded-lg border bg-card text-card-foreground", this.class)}>        <button type="button" class="flex w-full items-center gap-2 px-3 py-2 text-sm" onClick={() => { this.toggle(); }}>          <span class={() => cn("inline-block shrink-0 transition-transform", this._open && "rotate-90")}>            <Icon name="ChevronRight" size={14} />          </span>          <span            class={cn(              "flex size-4 shrink-0 items-center justify-center rounded-full text-[10px] leading-none",              "in-data-[status=done]:bg-primary in-data-[status=done]:text-primary-foreground",              "in-data-[status=running]:animate-pulse in-data-[status=running]:bg-primary/30",              "in-data-[status=pending]:bg-muted",              "in-data-[status=error]:bg-destructive in-data-[status=error]:text-destructive-foreground",            )}          >            {() =>              this.status === "done" ? (                <Icon name="Check" size={10} />              ) : this.status === "error" ? (                <Icon name="X" size={10} />              ) : (                ""              )            }          </span>          <span class="flex-1 text-left font-medium">{this.title}</span>        </button>        <div          data-state={() => (this._open ? "open" : "closed")}          class="flex flex-col gap-1.5 px-3 pb-3 pl-9 text-xs text-muted-foreground data-[state=closed]:hidden"        >          {this.children}        </div>      </div>    );  }}

Install the following dependencies:

npm install @praxisjs/css @morphos/icons
pnpm add @praxisjs/css @morphos/icons
yarn add @praxisjs/css @morphos/icons
bun add @praxisjs/css @morphos/icons

Copy and paste the following code into your project.

task.tsx
import { StatefulComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class TaskStyles extends Stylesheet {  $root = this.css({    borderRadius: "0.5rem",    border: `1px solid ${t.border}`,    backgroundColor: t.card,    color: t.cardForeground,  });  $trigger = this.css({    display: "flex",    width: "100%",    alignItems: "center",    gap: "0.5rem",    padding: "0.5rem 0.75rem",    fontSize: "0.875rem",    cursor: "pointer",  });  $chevron = this.css({ display: "inline-block", flexShrink: 0, transition: "transform 150ms ease" }).on("&[data-open]", {    transform: "rotate(90deg)",  });  $dot = this.css({    display: "flex",    height: "1rem",    width: "1rem",    flexShrink: 0,    alignItems: "center",    justifyContent: "center",    borderRadius: "9999px",    fontSize: "0.625rem",    lineHeight: 1,    backgroundColor: t.muted,  })    .on('[data-status="done"] &', { backgroundColor: t.primary, color: t.primaryForeground })    .on('[data-status="running"] &', { backgroundColor: `color-mix(in oklab, ${t.primary} 30%, transparent)` })    .on('[data-status="error"] &', { backgroundColor: t.destructive, color: t.destructiveForeground });  $title = this.css({ flex: "1 1 0%", textAlign: "left", fontWeight: 500 });  $content = this.css({    display: "flex",    flexDirection: "column",    gap: "0.375rem",    padding: "0 0.75rem 0.75rem 2.25rem",    fontSize: "0.75rem",    color: t.mutedForeground,  }).on('&[data-state="closed"]', { display: "none" });}export type TaskStatus = "pending" | "running" | "done" | "error";export interface TaskProps {  title: string;  status?: TaskStatus;  defaultOpen?: boolean;  class?: string;  children?: Children;}@Component()export class Task extends StatefulComponent {  @Styled(TaskStyles) $s!: TaskStyles;  @Prop() title = "";  @Prop() status: TaskStatus = "pending";  @Prop() defaultOpen = false;  @Prop() class?: string;  @Prop() children?: Children;  @State() _open = false;  onBeforeMount() {    this._open = this.defaultOpen;  }  toggle(): void {    this._open = !this._open;  }  render() {    return (      <div data-slot="task" data-status={() => this.status} class={cx(this.$s.$root, this.class)}>        <button type="button" class={this.$s.$trigger} onClick={() => { this.toggle(); }}>          <span data-open={() => (this._open ? "" : undefined)} class={this.$s.$chevron}>            <Icon name="ChevronRight" size={14} />          </span>          <span class={this.$s.$dot}>            {() =>              this.status === "done" ? (                <Icon name="Check" size={10} />              ) : this.status === "error" ? (                <Icon name="X" size={10} />              ) : (                ""              )            }          </span>          <span class={this.$s.$title}>{this.title}</span>        </button>        <div data-state={() => (this._open ? "open" : "closed")} class={this.$s.$content}>          {this.children}        </div>      </div>    );  }}

Examples

About

Purely presentational — no Morphos equivalent. Distinct from Steps (a flat non-collapsible list of many steps) and ChainOfThought (reasoning text, not task status): this is one named task whose status is expected to change live as it runs.

Usage

import { Task } from "@/components/ui/task";

<Task title="Refactor auth module" status="running" defaultOpen>
  <span>src/auth/login.ts</span>
  <span>src/auth/session.ts</span>
</Task>

Props

PropTypeDefault
titlestring
status"pending" | "running" | "done" | "error""pending"
defaultOpenbooleanfalse
classstring

On this page