Fundamentals

Concepts

The mental model behind taw-ui: tool call lifecycle, AI-native fields, and the patterns that make interactive components work in chat UIs.

Tool Call Lifecycle

Every AI tool call goes through a lifecycle. SDKs represent this as a part object with a state field. taw-ui components handle all four states from a single prop — no conditional rendering needed.

Loadinginput-available

Tool was called but hasn't returned yet. Component shows a skeleton.

Streamingstreaming

Partial data is arriving. Component renders what it can, skeletons for the rest.

Outputoutput-available

Tool returned successfully. Component validates and renders the full result.

!Schema Validation Failed

taw-ui: KpiCard received invalid data

missingstatsexpected invalid_type
Erroroutput-error

Tool failed or returned invalid data. Component shows a helpful error panel.

!Tool Error

Connection timeout after 30s

The tool returned an error instead of data. This usually means the upstream API failed — retry or check the tool's logs.

// Your component code stays clean
function ToolResult({ part }: { part: TawToolPart }) {
  // No switch statements, no if/else chains
  // The component handles all 4 states internally
  return <KpiCard part={part} />
}

The Part Object

TawToolPart is the universal shape for tool call data. It matches the structure used by Vercel AI SDK, Anthropic SDK, and OpenAI SDK — so you can pass SDK parts directly without transformation.

interface TawToolPart<TInput = unknown, TOutput = unknown> {
  id: string           // Unique call ID
  toolName: string     // Which tool was called
  input: TInput        // Arguments sent to the tool
  output?: TOutput     // Result data (when available)
  error?: Error | string  // Error (when failed)
  state:               // Current lifecycle state
    | "input-available"   // Called, waiting
    | "streaming"         // Partial data arriving
    | "output-available"  // Complete result
    | "output-error"      // Failed
}

Confidence & Caveat

AI outputs aren't always certain. Every taw-ui schema supports two fields for handling uncertainty: confidence (0–1) for developers, and caveat for humans.

confidencenumber (0–1), optional

Machine-readable signal for developers. Use it to filter, threshold, or decide when to set a caveat. Never rendered as a number in the UI.

caveatstring, optional

Human-readable uncertainty note from the AI. Only set when there's something to say — silence is confidence.

// Server: the AI speaks to humans, not in metrics
return {
  label: "Revenue",
  value: 142580,
  confidence: 0.65,  // DX: developers use this for thresholds
  caveat: "Based on partial data — full sync completes tonight",
}

// High confidence? Don't set caveat. Silence is confidence.

Source Provenance

Where did this data come from? The optional source field lets your tools declare their data origin. Components render this as a subtle footer with the source name, freshness timestamp, and optional link.

// Included in every schema
source: {
  label: "Stripe Dashboard",   // Required: what produced this data
  freshness: "2 hours ago",     // Optional: how stale is it
  url: "https://dashboard..."   // Optional: link to source
}

This matters because AI tools often aggregate from multiple sources. Users should always be able to trace a number back to its origin — especially in financial, medical, or compliance contexts.

Schema Validation

LLMs sometimes return wrong shapes. taw-ui validates tool output at render time using the same Zod schema you used on the server. When validation fails, the component renders a helpful error — never silently returns null.

Strict parse (render time)
// Used when state = "output-available"
const result = KpiCard.parse(output)

if (!result.ok) {
  // result.error has field-level details
  // + "Did you mean?" suggestions
  return <TawError parseError={result.error} />
}

// result.data is fully typed
Lenient parse (streaming)
// Used when state = "streaming"
const partial = KpiCard.safeParse(output)

// Returns typed data or null
// No errors — partial data is expected
// Component renders what it can

Two parse modes solve the streaming problem: strict mode catches schema mismatches after the tool completes; lenient mode gracefully handles incomplete data while streaming.

The Receipt Pattern

In chat UIs, interactive components (choice lists, forms, confirmations) have a problem: after the user decides, the full component wastes vertical space. The receipt pattern solves this — after a decision, the component collapses into a compact, read-only summary of what was chosen.

How it works
1AI presents options via OptionList
2User selects and confirms — onAction fires with the decision
3You create a TawReceipt and pass it back as a prop
4Component collapses to a compact receipt — scroll back and it's just one line
import { OptionList } from "@/components/taw/option-list"
import type { TawReceipt } from "taw-ui"

const [receipt, setReceipt] = useState<TawReceipt>()

<OptionList
  part={part}
  onAction={(actionId, payload) => {
    // payload.receipt is auto-generated
    setReceipt(payload.receipt)
  }}
  receipt={receipt}  // When set, collapses to receipt
/>

Actions

Actions are the buttons in interactive components. Each action has an ID, label, and optional variant. taw-ui auto-resolves variants from semantic IDs — cancel, dismiss, skip automatically render as ghost buttons. Primary actions render as filled buttons.

// Actions defined in tool output schema
actions: [
  { id: "deploy", label: "Deploy to production" },
  { id: "cancel", label: "Cancel" },
  // deploy → primary (default)
  // cancel → ghost (auto-detected from ID)
]

// Override with explicit variant
{ id: "delete", label: "Delete all", variant: "destructive" }

The confirmLabel field adds a two-step confirmation for dangerous actions — first click shows the confirm label, second click executes.

Routing Tool Calls

When your app has multiple tools, route each tool call to the right component using the toolName from the part object.

import { KpiCard } from "@/components/taw/kpi-card"
import { DataTable } from "@/components/taw/data-table"
import { OptionList } from "@/components/taw/option-list"
import type { TawToolPart } from "taw-ui"

function ToolOutput({ part }: { part: TawToolPart }) {
  switch (part.toolName) {
    case "getMetrics":
      return <KpiCard part={part} />
    case "showTable":
      return <DataTable part={part} />
    case "chooseAction":
      return <OptionList part={part} />
    default:
      return null
  }
}

Design Tokens

taw-ui uses CSS custom properties for theming — no Tailwind theme extension, no build-time config. The default theme ships with Dracula-inspired dark mode and Alucard-inspired light mode. Override any token to match your design system. All colors use oklch for perceptual uniformity.

TokenPurposePreview
--taw-surfaceDefault component background
--taw-surface-raisedElevated elements (cards, modals)
--taw-surface-sunkenRecessed areas (page bg, code blocks)
--taw-borderAll borders and dividers
--taw-text-primaryHeadings and important text
--taw-text-mutedSecondary text and labels
--taw-accentInteractive elements, links, badges
--taw-successPositive states, confirmations
--taw-warningCaution states, amber alerts
--taw-errorError states, destructive actions
--taw-cyanInfo highlights, special badges
--taw-pinkEmphasis, decorative accents
--taw-yellowHighlights, attention markers

What's Next

  • KpiCard — Start with the simplest component
  • DataTable — Rich tabular data with 9 column types
  • OptionList — See the receipt pattern in action