Domain Surfaces
Canonical UI surfaces for real-world entity types. Provider adapters underneath.
taw-ui started with generic AI tool result components — KpiCard, DataTable, InsightCard. Domain Surfaces extend this into integration-ready surfaces for specific entity categories: issues, events, posts, contacts, and more.
What are Domain Surfaces?
A domain surface is a canonical UI component for a category of real-world entities. Instead of building provider-specific components (GithubIssueCard, LinearIssueCard), we build one canonical surface per entity type and layer provider adapters underneath.
Architecture
Each domain surface consists of three layers:
A Zod schema defining the normalized shape for the entity type. Provider-agnostic, validated at render time. This is the contract.
A polished, motion-native component that renders the canonical data. Provider-aware (shows GitHub/Linear icons) but not provider-dependent.
Pure transformation functions that map raw provider data into the canonical schema. No auth, no API calls, no side effects. Your app fetches; taw-ui normalizes.
taw-ui vs. Your App
taw-ui is intentionally not an auth library. The boundary is clear:
- Canonical schemas & types
- React components & rendering
- Schema validation
- Provider adapters (pure transforms)
- Motion & visual polish
- Loading, error, and streaming states
- OAuth / authentication
- Access & refresh tokens
- API clients & SDKs
- Data fetching
- Permissions & scopes
- Error handling & retries
How It Works
// 1. Your app authenticates with the provider
const octokit = new Octokit({ auth: userToken })
// 2. Your app fetches the data
const { data: issue } = await octokit.issues.get({
owner: "vercel",
repo: "next.js",
issue_number: 58234,
})
// 3. Map API response to IssueCard schema
const output = {
id: issue.number.toString(),
title: issue.title,
body: issue.body,
status: { label: issue.state },
author: { login: issue.user.login, avatar: issue.user.avatar_url },
labels: issue.labels.map(l => ({ name: l.name, color: l.color })),
}
// 4. taw-ui renders it
<IssueCard part={{
toolCallId: "1",
toolName: "getIssue",
state: "output-available",
input: {},
output,
}} />Live Examples
Design Philosophy
Category first, provider second. We start with a canonical IssueCard, not a GithubIssueCard. The schema defines what an issue looks like across all providers. Adapters handle the differences.
Pure transformations. Provider adapters are pure functions — inline data mappings with no network calls, no auth, no side effects. They take raw provider data in and return canonical data out.
Graceful degradation. Every field beyond id, provider, title, and status is optional. The component renders beautifully with minimal data and gets richer as more fields are provided.
No SDK lock-in. Adapter input types are loose interfaces, not imports from @octokit/types or @linear/sdk. Any object with the right shape works.