Typescript Best Practices

by alleneubank2921eb8a685aNo license52 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 3 months ago

Use when reading or writing TypeScript or JavaScript files (.ts, .tsx, .js, tsconfig.json).

ArchivedInstructions onlySoftware Development
AI-generated overview

Guides TypeScript and JavaScript coding with type-first, functional and error-handling patterns.

What it does
This skill supplies language-level TypeScript and JavaScript idioms, including discriminated unions, branded types, const assertions, exhaustive switch checks, and Zod runtime validation. It recommends pairing with a separate React skill for component work and mentions optional type-fest utilities. It produces guidance and code patterns rather than files or scripts.
When to use it
Use it when reading or writing TypeScript or JavaScript files such as .ts, .tsx, .js, or tsconfig.json. It is intended for language-specific patterns, not React component design, which it defers to another skill.
Requirements
No scripts or tooling are required beyond the agent. The guidance references the Zod library and optionally type-fest, and assumes a project using TypeScript or JavaScript.

TypeScript Best Practices

Follows type-first, functional, and error handling patterns from CLAUDE.md. This skill covers language-specific idioms only.

Pair with React Best Practices

When working with React components (.tsx, .jsx files or @react imports), always load react-best-practices alongside this skill. This skill covers TypeScript fundamentals; React-specific patterns (effects, hooks, refs, component design) are in the dedicated React skill.

Make Illegal States Unrepresentable

Use the type system to prevent invalid states at compile time.

Discriminated unions for mutually exclusive states:

ts
// Good: only valid combinations possibletype RequestState<T> =  | { status: 'idle' }  | { status: 'loading' }  | { status: 'success'; data: T }  | { status: 'error'; error: Error };
// Bad: allows invalid combinations like { loading: true, error: Error }type RequestState<T> = {  loading: boolean;  data?: T;  error?: Error;};

Branded types for domain primitives:

ts
type UserId = string & { readonly __brand: 'UserId' };type OrderId = string & { readonly __brand: 'OrderId' };
// Compiler prevents passing OrderId where UserId expectedfunction getUser(id: UserId): Promise<User> { /* ... */ }

Const assertions for literal unions:

ts
const ROLES = ['admin', 'user', 'guest'] as const;type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
// Array and type stay in sync automaticallyfunction isValidRole(role: string): role is Role {  return ROLES.includes(role as Role);}

Exhaustive switch with never check:

ts
type Status = "active" | "inactive";
function processStatus(status: Status): string {  switch (status) {    case "active":      return "processing";    case "inactive":      return "skipped";    default: {      const _exhaustive: never = status;      throw new Error(`unhandled status: ${_exhaustive}`);    }  }}

Runtime Validation with Zod

  • Define schemas as single source of truth; infer TypeScript types with z.infer<>. Avoid duplicating types and schemas.
  • Use safeParse for user input where failure is expected; use parse at trust boundaries where invalid data is a bug.
  • Compose schemas with .extend(), .pick(), .omit(), .merge() for DRY definitions.
  • Add .transform() for data normalization at parse time (trim strings, parse dates).
ts
import { z } from "zod";
const UserSchema = z.object({  id: z.string().uuid(),  email: z.string().email(),  name: z.string().min(1),  createdAt: z.string().transform((s) => new Date(s)),});
type User = z.infer<typeof UserSchema>;
// Strict parsing at trust boundaries — throws if API contract violatedexport async function fetchUser(id: string): Promise<User> {  const response = await fetch(`/api/users/${id}`);  if (!response.ok) {    throw new Error(`fetch user ${id} failed: ${response.status}`);  }  return UserSchema.parse(await response.json());}
// Caller handles both success and error from user inputconst result = UserSchema.safeParse(formData);if (!result.success) {  setErrors(result.error.flatten().fieldErrors);  return;}

Optional: type-fest

For advanced type utilities beyond TypeScript builtins, consider type-fest:

  • Opaque<T, Token> - cleaner branded types than manual & { __brand } pattern
  • PartialDeep<T> - recursive partial for nested objects
  • ReadonlyDeep<T> - recursive readonly for immutable data
  • SetRequired<T, K> / SetOptional<T, K> - targeted field modifications
  • Simplify<T> - flatten complex intersection types in IDE tooltips
ts
import type { Opaque, PartialDeep } from 'type-fest';
type UserId = Opaque<string, 'UserId'>;type UserPatch = PartialDeep<User>;

Source and attribution

Source:alleneubank/claude-codein.claude/skills/typescript-best-practicesat commit2921eb8

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

Typescript Best Practices Agent Skill | SourceWeft