All resources
TypeScriptIntermediate2 min read

TypeScript Patterns for UI Code

Discriminated unions, satisfies with as const, generic components and exhaustive switches — with examples from this codebase.

Discriminated unions for variant data

When objects come in shapes, give each shape a literal tag. TypeScript narrows on the tag, so each branch only sees the fields that exist.

TypeScript
type ResourceBlock =  | { type: "paragraph"; text: string }  | { type: "code"; language: string; code: string }  | { type: "table"; columns: string[]; rows: string[][] };function render(block: ResourceBlock) {  if (block.type === "code") {    return highlight(block.code, block.language); // narrowed  }}

satisfies + as const for config

satisfies validates an object against a type without widening it, and as const keeps literal values. Together they give you both type-checking and precise inferred types — perfect for registries.

collections.tsTypeScript
export const SNIPPET_COLLECTIONS = [  { slug: "buttons", title: "Buttons", cardSize: "compact" },  { slug: "cards", title: "Cards", cardSize: "wide" },] as const satisfies readonly SnippetCollection[];// "buttons" | "cards" — derived, never out of dateexport type CollectionSlug = (typeof SNIPPET_COLLECTIONS)[number]["slug"];

Exhaustive switches

Return from every case and let the function's return type do the work. When a new variant is added, the compiler points at every switch that must handle it.

TypeScript
function assertNever(value: never): never {  throw new Error(`Unhandled value: ${JSON.stringify(value)}`);}function label(status: "success" | "error" | "cancelled") {  switch (status) {    case "success": return "Success";    case "error": return "Failed";    case "cancelled": return "Cancelled";    default: return assertNever(status);  }}

Generic components

A generic prop type lets one component stay strictly typed for any set of values — the caller's literal union flows into onValueChange.

TSX
type Option<Value extends string> = { value: Value; label: string };function SegmentedControl<Value extends string>(props: {  options: Option<Value>[];  value: Value;  onValueChange: (value: Value) => void;}) {  /* ... */}<SegmentedControl  options={[{ value: "dark", label: "Dark" }, { value: "light", label: "Light" }]}  value={appearance}  onValueChange={setAppearance} // (value: "dark" | "light") => void/>

Compiler options worth enabling

OptionWhat it catches
strictImplicit any, unchecked null and undefined, unsafe this
noUncheckedIndexedAccessarray[i] and record[key] may be undefined
exactOptionalPropertyTypesPassing undefined where a prop must be omitted
verbatimModuleSyntaxType-only imports that would leak into runtime code