Step 2 — TypeScript Essentials for React
Before touching JSX, let's cover the handful of TypeScript patterns that show up in nearly every iKanban component. We'll read them straight out of src/components/AgentModeBadge.tsx — a small, real component that renders a colored badge describing whether an AI agent runs via CLI, API, both, or neither.
1. A union of string literals
export type AvailabilityMode = 'cli_only' | 'api_only' | 'both' | 'none';
This is not an enum — it's a type that can only ever be one of these four exact strings. Pass anything else and TypeScript rejects it at compile time. This pattern shows up everywhere in iKanban: task status, issue priority, agent lifecycle state — any field with a small, fixed set of valid values is modeled this way instead of a free-form string.
2. Props as an interface
interface AgentModeBadgeProps {
mode: AvailabilityMode;
className?: string;
showTooltip?: boolean;
}
export function AgentModeBadge({
mode,
className,
showTooltip = true,
}: AgentModeBadgeProps) {
// ...
}
Every component's inputs are an interface (or type), destructured directly in the function signature. className? and showTooltip? are optional — the ? means callers can omit them. showTooltip = true is a default value applied during destructuring, a plain JavaScript feature that pairs naturally with optional TS props.
3. A lookup object keyed by the union, typed as a const
const MODE_CONFIG = {
cli_only: { icon: Monitor, labelKey: 'settings.agents.mode.cliOnly', /* ... */ },
api_only: { icon: Cloud, labelKey: 'settings.agents.mode.apiOnly', /* ... */ },
both: { icon: RefreshCw, labelKey: 'settings.agents.mode.both', /* ... */ },
none: { icon: XCircle, labelKey: 'settings.agents.mode.none', /* ... */ },
} as const;
Because the object's keys are exactly the four AvailabilityMode values, MODE_CONFIG[mode] is guaranteed to exist for any valid mode — no undefined check needed. This is the standard way iKanban maps a union type to per-variant configuration (icon, label, styling) without a chain of if/else.
4. as const narrows literal types
Without as const, TypeScript would widen variant: 'secondary' to the general type string. With it, TypeScript keeps the exact literal 'secondary', which matters because the <Badge variant={...}> prop only accepts a specific set of literal strings, not any string.
Putting it together
const config = MODE_CONFIG[mode];
const Icon = config.icon;
return (
<Badge variant={config.variant}>
<Icon className="h-3 w-3" />
{t(config.labelKey)}
</Badge>
);
Icon here is a component reference stored in a variable, then rendered with <Icon />. This is how iKanban swaps icons dynamically based on data — you'll see the same trick in the sidebar icon map in Step 4.
Checkpoint
Open vibe-frontend/src/components/AgentModeBadge.tsx yourself and find:
- Where the optional
showTooltipprop changes what gets rendered (hint: an earlyreturn). - How
mode === 'both'conditionally appends extra Tailwind classes to the badge.
Continue to Step 3 — Vite, TypeScript Config & Project Structure, where we cover how these .tsx files get built and how @/ imports resolve.