TypeScript StyleGuide and Coding Conventions
May 8, 2026 ยท View on GitHub
Key Sections:
- Case (
camelCasevs.PascalCasevs.ALL_CAPS) - Interface names
- Test filenames
- Type files
nullvs.undefinedconstvs.letvs.var- Formatting
- Quotes (single vs. double)
- Use semicolons
- Arrays (annotate as
Type[]) typevs.interface- One-line
ifstatements importsKeyboardEvents- Components
- Function return type
- Hooks
Case
Variable and Function
- Use
camelCasefor variable and function names
Reason: Conventional JavaScript
Bad
var FooVar;
function BarFunc() {}
Good
var fooVar;
function barFunc() {}
Component
- Use
PascalCasefor component name, even if function component.
Reason: Follows React naming convention.
Bad
function component(): ReactElement {
return <subComponent />;
}
Good
function Component(): ReactElement {
return <SubComponent />;
}
Class
- Use
PascalCasefor class names.
Reason: This is actually fairly conventional in standard JavaScript.
Bad
class foo {}
Good
class Foo {}
- Use
camelCaseof class members and methods
Reason: Naturally follows from variable and function naming convention.
Bad
class Foo {
Bar: number;
Baz() {}
}
Good
class Foo {
bar: number;
baz() {}
}
Interface
- Use
PascalCasefor name.
Reason: Similar to class
- Use
camelCasefor members.
Reason: Similar to class
Type
- Use
PascalCasefor name.
Reason: Similar to class
- Use
camelCasefor members.
Reason: Similar to class
- Use
ALL_CAPSfor action types.
Reason: Follows Redux naming convention
Namespace
- Use
PascalCasefor names
Reason: Convention followed by the TypeScript team. Namespaces are effectively just a class with static members. Class names are
PascalCase=> Namespace names arePascalCase
Bad
namespace foo {}
Good
namespace Foo {}
Enum
- Use
PascalCasefor enum names
Reason: Similar to Class. Is a Type.
Bad
enum color {}
Good
enum Color {}
- Use
PascalCasefor enum member
Reason: Convention followed by TypeScript team i.e. the language creators e.g
SyntaxKind.StringLiteral. Also helps with translation (code generation) of other languages into TypeScript.
Bad
enum Color {
red,
}
Good
enum Color {
Red,
}
Filename
Name files and folders with camelCase (e.g., utilities.ts, index.tsx, tractor.png, components/), unless it is
the name of the contained Component (e.g., DataEntryTable.tsx, ReviewEntries/).
Reason:
camelCaseis conventional across many JS teams.
Interface names
- Don't prefix with
I
Reason: Unconventional.
lib.d.tsdefines important interfaces without anI(e.g. Window, Document etc).
Bad
interface IFoo {}
Good
interface Foo {}
Test filenames
-
The test file associated with
path/to/Component.tsxshould bepath/to/tests/Component.test.tsx. -
Auxiliary test files with data or mock objects should have filenames ending in
Mock.ts, e.g.,path/to/tests/DataMock.ts.
Reason:
*.test.*and*Mock.tsfiles are ignored by our CodeCov settings.
Type files
- Separate type files should contain
types,classes,interfaces,enums but not any classes or functions that need unit testings. Such files should have filenames ending inTypes.ts, e.g.,MergeDupsReduxTypes.ts.
Reason:
*Types.tsfiles are ignored by our CodeCov settings.
null vs. undefined
- Prefer not to use either for explicit unavailability
Reason: these values are commonly used to keep a consistent structure between values. In TypeScript you use types to denote the structure
Bad
let foo = { x: 123, y: undefined };
Good
let foo: { x: number; y?: number } = { x: 123 };
- Use
undefinedin general (do consider returning an object like{valid:boolean,value?:Foo}instead)
Bad
return null;
Good
return undefined;
- Use
nullwhere its a part of the API or conventional
Reason: It is conventional in Node.js e.g.
errorisnullfor NodeBack style callbacks.
Bad
cb(undefined);
Good
cb(null);
- Use truthy check for objects being
nullorundefined
Bad
if (error === null)
Good
if (error)
Remark: Use
===/!==to check fornull/undefinedon primitives that might be other falsy values (like'',0,false).
const vs. let vs. var
-
constallows the variable to be mutated, but doesn't allow it to be redeclared; preferconstany time your variable only needs to be declared once. -
letandvarboth allow redeclaration, butvaris a global variable andletis limited to the scope of its declaration; preferlettovar.
Bad
var shouldClapHands = false;
for (var i = 0; i < 3; i++) {
var you = getYouAtTime(i);
shouldClapHands ||= you.isHappy() && you.knowsIt();
}
Good
let shouldClapHands = false;
for (let i = 0; i < 3; i++) {
const you = getYouAtTime(i);
shouldClapHands ||= you.isHappy() && you.knowsIt();
}
Formatting
Use Prettier to format TypeScript code as described in the README.
Reason: Reduce the cognitive overload on the team.
Quotes
- Prefer double quotes (
"your text") to single quotes ('your text').
Reason: Follows Prettier naming convention.
Semicolons
- Use semicolons.
Reasons: Explicit semicolons helps language formatting tools give consistent results. Missing ASI (automatic semicolon insertion) can trip new devs e.g.
foo() \n (function(){})will be a single statement (not two). TC39 warning on this as well. Example teams: airbnb, idiomatic, google/angular, facebook/react, Microsoft/TypeScript.
Arrays
- Annotate arrays as
foos:Foo[]instead offoos:Array<Foo>.
Reasons: Its easier to read. Its used by the TypeScript team. Makes easier to know something is an array as the mind is trained to detect
[].
type vs. interface
- Use
typewhen you might need a union or intersection:
type Foo = number | { someProperty: number }
- Use
interfacewhen you wantextendsorimplementse.g
interface Foo {
foo: string;
}
interface FooBar extends Foo {
bar: string;
}
class X implements FooBar {
foo: string;
bar: string;
}
- Otherwise use whatever makes you happy that day.
One line if statements
- Add braces to one-line
ifstatements;
Good
if (isEmpty) {
callFun();
}
Bad
if (isEmpty)
callFun();
Reason: Avoiding braces can cause developers to miss bugs, such as Apple's infamous goto-fail bug
imports
Absolute vs. relative paths
- Use absolute
importstatements everywhere for consistency.
Good
import { type Project } from "api/models";
import { getAllProjects } from "backend";
Bad
import { type Project } from "../../../../api/models";
import { getAllProjects } from "../../../../backend";
Reason: Provides consistency for imports across all files and shortens imports of commonly used top level modules. Developers don't have to count
../to know where a module is, they can simply start from the root ofsrc/.
Specificity preferred
- Generally import the specific things needed (e.g., not
Reactwhen{ type ReactElement }will do), and from a more specific target (e.g.,from "api/models"rather thanfrom "api"):
Good
import { type ReactElement } from "react";
import { type Project } from "api/models";
function Component(props: { project: Project }): ReactElement {}
Bad
import React from "react";
import { type Project } from "api";
function Component(props: { project: Project }): React.ReactElement {}
KeyboardEvents
- Use
ts-key-enumwhen comparing toReact.KeyboardEvents.
Good
import { Key } from "ts-key-enum";
if (event.key === Key.Enter) {
}
Bad
if (event.key === "Enter") {
}
Reason: Avoid typos and increase the number of mistakes that can be caught at compile time.
Components
- Prefer functional components to class components.
Good
function Component(props: ComponentProps): ReactElement {
return <SubComponent />;
}
Bad
class Component extends React.Component<ComponentProps, ComponentState> {
render() {
return <SubComponent />;
}
}
Component Props
- Alphabetize React component props for consistency and readability.
Good
interface MyComponentProps {
className?: string;
id: string;
onClick: () => void;
title: string;
}
Bad
interface MyComponentProps {
title: string;
id: string;
onClick: () => void;
className?: string;
}
Reason: Consistent ordering makes props easier to find and reduces merge conflicts.
This convention applies to:
- Interface/type definitions for component props
- Prop destructuring in function parameters
- Props passed to components in JSX
Function return type
- Specify the return type of a named function, whether declared as a
functionorconst. - The return type of a functional component should be
ReactElement.
Good
import {ReactElement} from React;
function Component(props: { name: string }): ReactElement {
const sayHi = (name: string): string => {
return `Hi, ${name}!`;
}
return sayHi(props.name);
}
Bad
function Component(props: { name: string }) {
const sayHi = (name: string) => {
return `Hi, ${name}!`;
};
return sayHi(props.name);
}
Reason: Ensure all return paths are the expected type.
Hooks
- Prefer
useAppDispatchtouseDispatchanduseAppSelectortouseSelector.
Good
import { useAppDispatch, useAppSelector } from "rootRedux/hooks";
import { type StoreState } from "rootRedux/types";
function Component(): ReactElement {
const dispatch = useAppDispatch();
const subState = useAppSelector((state: StoreState) => state.subState);
}
Bad
import { useDispatch, useSelector } from "react-redux";
function Component(): ReactElement {
const dispatch = useDispatch();
const subState = useSelector((state) => state.subState);
}
Reason: See
types/hooks.ts.