Kosmesis
Components

Reasoning

A collapsible block for an AI response's raw reasoning/thinking text. Purely presentational — no Morphos equivalent.

Installation

npx kosmesis add reasoning
pnpm dlx kosmesis add reasoning
yarn dlx kosmesis add reasoning
bunx kosmesis add reasoning

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.

reasoning.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 interface ReasoningProps {  defaultOpen?: boolean;  duration?: number;  streaming?: boolean;  class?: string;  children?: Children;}// Distinct from `ChainOfThought` (a stepped list of named steps): this is a single collapsible block.@Component()export class Reasoning extends StatefulComponent {  @Prop() defaultOpen = false;  @Prop() duration?: number;  @Prop() streaming = 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="reasoning" class={cn("rounded-lg border bg-muted/30", this.class)}>        <button          type="button"          class="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground"          onClick={() => { this.toggle(); }}        >          <span class={() => cn("inline-block transition-transform", this._open && "rotate-90")}>            <Icon name="ChevronRight" size={14} />          </span>          {this.streaming ? "Thinking…" : this.duration !== undefined ? `Thought for ${String(this.duration)}s` : "Reasoning"}        </button>        <div          data-state={() => (this._open ? "open" : "closed")}          class="flex flex-col gap-2 border-t px-3 py-2 text-sm 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.

reasoning.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 ReasoningStyles extends Stylesheet {  $root = this.css({    borderRadius: "0.5rem",    border: `1px solid ${t.border}`,    backgroundColor: `color-mix(in oklab, ${t.muted} 30%, transparent)`,  });  $trigger = this.css({    display: "flex",    width: "100%",    alignItems: "center",    gap: "0.5rem",    padding: "0.5rem 0.75rem",    fontSize: "0.875rem",    color: t.mutedForeground,    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",    borderTop: `1px solid ${t.border}`,    padding: "0.5rem 0.75rem",    fontSize: "0.875rem",    color: t.mutedForeground,  }).on('&[data-state="closed"]', { display: "none" });}export interface ReasoningProps {  defaultOpen?: boolean;  duration?: number;  streaming?: boolean;  class?: string;  children?: Children;}// Distinct from `ChainOfThought` (a stepped list of named steps): this is a single collapsible block.@Component()export class Reasoning extends StatefulComponent {  @Styled(ReasoningStyles) $s!: ReasoningStyles;  @Prop() defaultOpen = false;  @Prop() duration?: number;  @Prop() streaming = 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="reasoning" 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>          {this.streaming ? "Thinking…" : this.duration !== undefined ? `Thought for ${String(this.duration)}s` : "Reasoning"}        </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 ChainOfThought (a stepped list of named steps): this is a single block, typically collapsed by default once streaming becomes false. The trigger label shows "Thinking…" while streaming, then "Thought for Ns" once duration is known.

Usage

import { Reasoning } from "@/components/ui/reasoning";

<Reasoning streaming={isStreaming} duration={elapsedSeconds}>
  The user is asking about refund windows...
</Reasoning>

Props

PropTypeDefault
defaultOpenbooleanfalse
durationnumber (seconds)
streamingbooleanfalse
classstring

On this page