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.
Tool was called but hasn't returned yet. Component shows a skeleton.
Partial data is arriving. Component renders what it can, skeletons for the rest.
Tool returned successfully. Component validates and renders the full result.
taw-ui: KpiCard received invalid data
Tool failed or returned invalid data. Component shows a helpful error panel.
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.
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.
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.
// 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// Used when state = "streaming"
const partial = KpiCard.safeParse(output)
// Returns typed data or null
// No errors — partial data is expected
// Component renders what it canTwo 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.
OptionListonAction fires with the decisionTawReceipt and pass it back as a propimport { 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.
| Token | Purpose | Preview |
|---|---|---|
| --taw-surface | Default component background | |
| --taw-surface-raised | Elevated elements (cards, modals) | |
| --taw-surface-sunken | Recessed areas (page bg, code blocks) | |
| --taw-border | All borders and dividers | |
| --taw-text-primary | Headings and important text | |
| --taw-text-muted | Secondary text and labels | |
| --taw-accent | Interactive elements, links, badges | |
| --taw-success | Positive states, confirmations | |
| --taw-warning | Caution states, amber alerts | |
| --taw-error | Error states, destructive actions | |
| --taw-cyan | Info highlights, special badges | |
| --taw-pink | Emphasis, decorative accents | |
| --taw-yellow | Highlights, 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