Migration Examples
Before-and-after examples of moving from override-heavy UI to semantic APIs.
These examples show the same UI written two ways: the common override approach, and the semantic approach. The semantic version is not shorter for its own sake — it is shorter because meaning was centralized where it belongs.
Status badge
Before — visual encoding at the callsite:
{status === 'failed' && (
<span className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-red-700 bg-red-100">
<span className="size-1.5 rounded-full bg-red-500" />
Failed
</span>
)}
{status === 'running' && (
<span className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-blue-700 bg-blue-100">
<span className="size-1.5 rounded-full bg-blue-500 animate-pulse" />
Running
</span>
)}After — semantic:
<Badge keyword={status} />The keyword resolves intent, appearance, dot animation, and label. The callsite states what the thing is. The component handles the rest.
Confirmation dialog buttons
Before:
<div className="flex gap-3">
<button className="px-4 py-2 rounded-lg border text-sm font-medium text-zinc-700">
Cancel
</button>
<button className="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700">
Delete account
</button>
</div>After:
<div className="flex gap-3">
<Button intent="neutral" appearance="outline">Cancel</Button>
<Button intent="danger">Delete account</Button>
</div>The intent props are legible to the next developer, to a design reviewer, and to an AI. The styling is consistent with every other destructive action in the system — not improvised for this specific dialog.
Inline alert
Before:
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
<p className="font-semibold">Approaching limit</p>
<p className="mt-1">You have used 90% of your monthly quota.</p>
</div>After:
<Alert intent="warning" title="Approaching limit">
You have used 90% of your monthly quota.
</Alert>The visual treatment is now a system decision, not a callsite decision. A future rebrand or dark mode improvement applies everywhere, not just here.
Connection status dot
Before:
<div className="flex items-center gap-1.5">
<div className="size-2 rounded-full bg-green-500" />
<span className="text-xs text-zinc-600">Connected</span>
</div>After:
<StatusIndicator state="success" label="Connected" />AI streaming state
Before:
{isStreaming ? (
<div className="flex items-center gap-2 text-sm text-zinc-500">
<div className="size-1.5 rounded-full bg-blue-500 animate-pulse" />
Generating...
</div>
) : (
<p className="text-sm text-zinc-700">{response}</p>
)}After:
<StreamingText content={response} isStreaming={isStreaming} />Or, for the badge:
<Badge keyword={isStreaming ? 'streaming' : 'done'} />The pattern
In every example above, the migration follows the same shape:
- Extract the meaning — what is this communicating?
- Find the system concept — which component and which prop expresses that?
- Replace the reconstruction with the declaration
The result is callsites that say what they mean, and components that know how to express it.