TypeScript
Patterns and conventions for all TypeScript code.
Types
import type {}for type-only imports:import type {FC} from 'react'
Naming — camelCase
All identifiers use camelCase: Zod fields, form name/id/htmlFor, props, state, params.
Exceptions (snake_case OK):
types/database.ts— mirrors DB column names- Dynamic template literal names where variable part is already lowercase
- Environment variable names (
SUPABASE_URL)
Map snake_case ↔ camelCase at API call boundaries, not in schemas or UI code.
Naming — Descriptive and Self-Documenting
Follow Apple's Swift API Design Guidelines: names should be clear at the point of use, reading like prose. Favor long, descriptive names over short or abbreviated ones. Code should be readable without consulting documentation.
- Functions and methods — imperative verb phrases:
calculateProgressPercentageFromCompletedSets,processUserOnboardingProfile - Parameters — role, not type:
totalSecondsnotn,emailAddressnots - Variables — what they hold:
restDurationInSeconds,submitButton,weightInputValue - No abbreviations — spell out unless universally known (
url,id,api):animationDurationInMillisecondsnotanimDur - No redundant words —
availableExercisesnotexerciseArray, but don't sacrifice clarity for brevity
Exception — React event handlers follow
handle{Action}{Element}from the react-code skill (e.g.handleClickSave,handleChangeInput) — the{Element}is required, since a barehandleClickorhandleChangetripsreact-doctor/no-generic-handler-names. The descriptive naming guidelines above apply to utilities, hooks, callbacks, and non-event-handler functions.
Read references/naming-conventions.md for extended BAD/GOOD examples of each naming pattern.
Exported Functions — Explicit Return Types
All exported functions must have explicit return types.
Exceptions:
- Route loaders/actions (complex generics)
- React components typed with
FC<Props>(return type provided by generic)
// BAD
export const formatDate = (date: Date) => format(date, 'yyyy-MM-dd');
// GOOD
export const formatDate = (date: Date): string => format(date, 'yyyy-MM-dd');
General Rules
- Use
typenotinterface— interfaces support declaration merging, which creates unpredictable behavior;typeis consistent and predictable - Arrays:
string[]notArray<string> - Boolean naming:
^((can|has|hide|is|show)[A-Z]|checked|disabled|required)
Code Patterns
- No
switchstatements — use if/else chains or object maps; switch requiresbreak, is prone to fallthrough bugs, and is harder to type-check exhaustively - No TypeScript enums — use
as constobjects with derived types; enums compile to runtime objects with surprising behavior and don't tree-shake well - JSX boolean props: always explicit
={true}— makes props grep-able and avoids confusion when a prop is later refactored to a non-boolean type - Max 3 function parameters — use an options object beyond that; call sites with 4+ positional args are hard to read and argument order mistakes are common
Zod
z.literal()notz.enum()— sort values alphanumerically
// BAD
z.enum(['metric', 'imperial']);
// GOOD
z.literal(['imperial', 'metric']);