Components
Chain of Thought
A collapsible list of an AI response's named reasoning steps. Purely presentational — no Morphos equivalent.
Installation
npx kosmesis add chain-of-thoughtpnpm dlx kosmesis add chain-of-thoughtyarn dlx kosmesis add chain-of-thoughtbunx kosmesis add chain-of-thoughtInstall the following dependencies:
npm install @morphos/iconspnpm add @morphos/iconsyarn add @morphos/iconsbun add @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent, StatelessComponent } 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 interface ChainOfThoughtProps { defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class ChainOfThought extends StatefulComponent { @Prop() defaultOpen = true; @Prop() class?: string; @Prop() children?: Children; @State() _open = true; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="chain-of-thought" 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 font-medium" onClick={() => { this.toggle(); }} > <span class={() => cn("inline-block transition-transform", this._open && "rotate-90")}> <Icon name="ChevronRight" size={14} /> </span> Chain of thought </button> <div data-state={() => (this._open ? "open" : "closed")} class="flex flex-col gap-2 px-3 pb-3 text-sm data-[state=closed]:hidden" > {this.children} </div> </div> ); }}export type ChainOfThoughtStepStatus = "complete" | "active" | "pending";export interface ChainOfThoughtStepProps { status?: ChainOfThoughtStepStatus; class?: string; children?: Children;}@Component()export class ChainOfThoughtStep extends StatelessComponent<ChainOfThoughtStepProps> { render() { const { status = "complete", class: cls, children } = this.props; return ( <div data-status={status} class={cn("flex items-start gap-2 text-muted-foreground", cls)}> <span class={cn( "mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full text-[10px] leading-none", "in-data-[status=complete]:bg-primary in-data-[status=complete]:text-primary-foreground", "in-data-[status=active]:animate-pulse in-data-[status=active]:bg-primary/20", "in-data-[status=pending]:bg-muted", )} > {status === "complete" ? <Icon name="Check" size={10} /> : ""} </span> <span class="in-data-[status=complete]:text-foreground">{children}</span> </div> ); }}Install the following dependencies:
npm install @praxisjs/css @morphos/iconspnpm add @praxisjs/css @morphos/iconsyarn add @praxisjs/css @morphos/iconsbun add @praxisjs/css @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent, StatelessComponent } 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 ChainOfThoughtStyles 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", fontWeight: 500, cursor: "pointer", }); $chevron = this.css({ display: "inline-block", transition: "transform 150ms ease" }).on("&[data-open]", { transform: "rotate(90deg)", }); $content = this.css({ display: "flex", flexDirection: "column", gap: "0.5rem", padding: "0 0.75rem 0.75rem", fontSize: "0.875rem", }).on('&[data-state="closed"]', { display: "none" }); $step = this.css({ display: "flex", alignItems: "flex-start", gap: "0.5rem", color: t.mutedForeground }); $stepDot = this.css({ marginTop: "0.125rem", 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="complete"] &', { backgroundColor: t.primary, color: t.primaryForeground }) .on('[data-status="active"] &', { backgroundColor: `color-mix(in oklab, ${t.primary} 20%, transparent)` }); $stepLabel = this.css({}).on('[data-status="complete"] &', { color: t.foreground });}export interface ChainOfThoughtProps { defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class ChainOfThought extends StatefulComponent { @Styled(ChainOfThoughtStyles) $s!: ChainOfThoughtStyles; @Prop() defaultOpen = true; @Prop() class?: string; @Prop() children?: Children; @State() _open = true; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="chain-of-thought" 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> Chain of thought </button> <div data-state={() => (this._open ? "open" : "closed")} class={this.$s.$content}> {this.children} </div> </div> ); }}export type ChainOfThoughtStepStatus = "complete" | "active" | "pending";export interface ChainOfThoughtStepProps { status?: ChainOfThoughtStepStatus; class?: string; children?: Children;}@Component()export class ChainOfThoughtStep extends StatelessComponent<ChainOfThoughtStepProps> { @Styled(ChainOfThoughtStyles) $s!: ChainOfThoughtStyles; render() { const { status = "complete", class: cls, children } = this.props; return ( <div data-status={status} class={cx(this.$s.$step, cls)}> <span class={this.$s.$stepDot}>{status === "complete" ? <Icon name="Check" size={10} /> : ""}</span> <span class={this.$s.$stepLabel}>{children}</span> </div> ); }}Examples
About
Purely presentational — no Morphos equivalent. Distinct from Reasoning (a single collapsible
text block): this is a stepped list of named steps, each with its own status.
Usage
import { ChainOfThought, ChainOfThoughtStep } from "@/components/ui/chain-of-thought";
<ChainOfThought>
<ChainOfThoughtStep status="complete">Searched the docs for "refund policy"</ChainOfThoughtStep>
<ChainOfThoughtStep status="active">Drafting a response</ChainOfThoughtStep>
<ChainOfThoughtStep status="pending">Reviewing tone</ChainOfThoughtStep>
</ChainOfThought>Props
ChainOfThought
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | true |
class | string | — |
ChainOfThoughtStep
| Prop | Type | Default |
|---|---|---|
status | "complete" | "active" | "pending" | "complete" |
class | string | — |