from a dead wiki page to a lived standard
Most TypeScript style guides end up as a long wiki page, written once and never read again. A style guide that actually works defines a clear scope, enforces rules automatically with ESLint and shows conventions through real examples instead of abstract prose.
Table of Contents
- 1. Why Most Style Guides Fail
- 2. Defining Scope: What a Style Guide Should Cover
- 3. Enforcing Rules Automatically Instead of Just Writing Them Down
- 4. Examples Instead of Prose: Right and Wrong Side by Side
- 5. Naming Conventions for Types, Interfaces and Generics
- 6. Structure Rules: File Organization and Export Patterns
- 7. Actively Maintaining the Style Guide Instead of Writing It Once
- 8. Introducing an Existing Style Guide to a Team
- 9. Style Guide Approaches Compared
- 10. Summary
- 11. FAQ
1. Why Most Style Guides Fail
A TypeScript style guide rarely fails due to lack of willingness, but because of the form in which it is created. A one off wiki document with long prose paragraphs gets read when it is written and never again afterward, because it plays no role in daily work. New rules end up in pull request comments instead of flowing into the style guide, and the gap between document and practice grows with every sprint.
An effective TypeScript style guide differs from a dead wiki page in three ways. It gets enforced automatically instead of merely recommended. It shows examples instead of formulating rules in prose. And it lives in the repository, versioned alongside the code, instead of gathering dust in a separate documentation system.
2. Defining Scope: What a Style Guide Should Cover
The most common mistake when creating a TypeScript style guide is too broad a scope. A guide that wants to cover formatting, architecture and business conventions all at once becomes unwieldy and nobody reads it in full. Formatting questions like indentation or quote style do not belong in a style guide, they belong in the Prettier configuration, where they get applied automatically without discussion.
A TypeScript style guide should focus on decisions Prettier cannot make automatically: when to use interface instead of type alias, how generics get named, which export patterns apply to modules, and how any gets handled. That narrows the guide down to what actually needs discussion and convention, instead of repeating formatting questions a tool already solves.
{
"//": "prettier.config.json — handles formatting so the style guide doesn't have to",
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}
3. Enforcing Rules Automatically Instead of Just Writing Them Down
A rule that only exists in the style guide but is not automatically checked disappears from the team's collective memory after a few weeks. Every rule in the TypeScript style guide should therefore answer the question of how it can be enforced with ESLint or a custom rule. Rules that cannot be automated should be critically questioned as to whether they really belong in the guide.
ESLint flat config makes it easy to place project specific rules right next to the style guide and version both together. A comment in the configuration that links back to the corresponding section in the style guide closes the gap between rule and rationale that pure ESLint configs often lack.
// eslint.config.ts — rules cross-reference the style guide sections
import tseslint from "typescript-eslint";
export default tseslint.config({
rules: {
// Style Guide §5.1: prefer interface for object shapes, type for unions
"@typescript-eslint/consistent-type-definitions": ["error", "interface"],
// Style Guide §5.3: generic type parameters use single uppercase letters
// with a descriptive suffix, e.g. TEntity, not T1 or Generic
"@typescript-eslint/naming-convention": [
"error",
{ selector: "typeParameter", format: ["PascalCase"], prefix: ["T"] },
],
// Style Guide §8.2: no default exports, always named exports
"import/no-default-export": "error",
// Style Guide §9.1: explicit any requires a linked ticket comment
"@typescript-eslint/no-explicit-any": "error",
},
});
4. Examples Instead of Prose: Right and Wrong Side by Side
Developers read code examples faster and retain them better than long prose explanations. A TypeScript style guide that shows every rule with a short, runnable before and after example gets actually used as a reference, while pure text rules usually go unread.
It matters that the examples come from real situations in the project, not from academic toy cases. An example showing a real review problem from the team's own codebase convinces the team more than a generic textbook example that has nothing to do with their own code.
// Style Guide §6.2: readonly arrays for data that must not be mutated
// WRONG — array can be mutated anywhere it is passed
function renderTags(tags: string[]): string {
return tags.join(", ");
}
// RIGHT — readonly signals intent and prevents accidental push/splice
function renderTagsSafely(tags: readonly string[]): string {
return tags.join(", ");
}
// Style Guide §6.4: prefer discriminated unions over optional flags
// WRONG — invalid states like { loading: true, error: "x" } are representable
interface FetchStateWrong {
loading?: boolean;
error?: string;
data?: unknown;
}
// RIGHT — only valid combinations can be constructed
type FetchState =
| { status: "loading" }
| { status: "error"; error: string }
| { status: "success"; data: unknown };
5. Naming Conventions for Types, Interfaces and Generics
Inconsistent naming conventions are one of the most common sources of discussion in reviews, even though a clear style guide can eliminate them entirely. Questions like whether an interface carries an I prefix, whether generics are named IEntity or TEntity, and whether enum values are written in UPPER_CASE or PascalCase should be decided once and then enforced via ESLint.
A proven convention is to give generics a descriptive T prefix, such as TEntity instead of just T, once more than one type parameter is in play. That makes the signatures of complex functions more readable, without every single caller having to study the implementation to understand the meaning of the type parameters.
// Style Guide §5: naming conventions in one place
// Interfaces: no I-prefix, PascalCase noun
interface OrderSummary { id: string; total: number }
// Generics with more than one parameter: descriptive T-prefix, not T1/T2
function mapEntries<TKey extends string, TValue>(
entries: readonly [TKey, TValue][],
): Record<TKey, TValue> {
return Object.fromEntries(entries) as Record<TKey, TValue>;
}
// Enum values: PascalCase, not UPPER_CASE, matches our TypeScript-first style
enum OrderStatus {
Draft = "draft",
Placed = "placed",
Shipped = "shipped",
}
// Type aliases for unions: suffix with the concept, not with "Type"
type PaymentMethod = "card" | "paypal" | "invoice"; // not PaymentMethodType
// Boolean-returning functions: is/has/can prefix, no bare adjectives
function isRefundable(order: OrderSummary): boolean {
return order.total > 0;
}
6. Structure Rules: File Organization and Export Patterns
Besides names and type patterns, a TypeScript style guide should also define where types live in the project. A proven convention is to keep types as close as possible to the code that uses them, instead of maintaining a central types.ts file that becomes unwieldy as the project grows. Only truly shared domain types move into a dedicated shared directory.
Export patterns also deserve a clear rule. Named exports instead of default exports ease refactoring, because IDEs track renames more reliably through named exports. This rule, like many structural rules in the style guide, can be enforced directly with an ESLint rule instead of hoping for discipline in review.
// Style Guide §8: co-locate types, only shared domain types move out
// src/features/orders/order.types.ts — lives next to its feature
export interface Order { id: string; total: number }
// src/shared/types/money.ts — genuinely shared across features, moved out
export interface Money { amount: number; currency: string }
// WRONG per Style Guide §8.2 — default export makes renames harder to track
export default function formatOrder(order: Order): string {
return `#${order.id}: ${order.total}`;
}
// RIGHT — named export, IDE refactors track this reliably
export function formatOrderSafely(order: Order): string {
return `#${order.id}: ${order.total}`;
}
7. Actively Maintaining the Style Guide Instead of Writing It Once
A TypeScript style guide is never finished. New language features, new libraries and new lessons from production incidents change which conventions make sense. A style guide that lives in the repository next to the code and gets updated through normal pull requests stays alive, because changes go through the same review process as code changes.
A simple mechanism to enforce maintenance is a requirement to add a style guide section alongside every new ESLint rule. That way the rule set and the documentation always grow in sync, instead of one lagging behind the other.
8. Introducing an Existing Style Guide to a Team
A new TypeScript style guide should never roll out overnight with full ESLint strictness over an existing codebase. A gentler path is to configure new rules as warn instead of error first, and only raise them to error after a transition period once existing violations are cleaned up.
Equally important is a short, joint presentation of the style guide in a team meeting, instead of simply sending it as a link. A team that understands why a rule exists is more likely to accept it than if ESLint suddenly throws unexplained errors in existing pull requests.
9. Style Guide Approaches Compared
The form of a TypeScript style guide decides its success more than its content. The following overview shows which practices turn a style guide into a dead wiki page and which ones actually keep it alive.
| Aspect | Dead Style Guide | Lived Style Guide | Effect |
|---|---|---|---|
| Location | Separate wiki system | In the repository, next to the code | Gets updated with pull requests |
| Format | Long prose paragraphs | Short before and after code examples | Faster to read and retain |
| Enforcement | Only a recommendation in review | ESLint rule per style guide section | Violations surface automatically |
| Scope | Formatting and architecture mixed together | Only decisions Prettier cannot make | Clear and focused |
| Rollout | Full strictness overnight | Gradually from warn to error | Acceptance instead of frustration |
10. Summary
A TypeScript style guide only works if it avoids the three central traps that turn most guides into dead wiki pages: missing automation, too much prose and too broad a scope. Teams that tie rules directly to ESLint, use examples instead of prose, and limit the guide to what Prettier does not already handle end up with a document that actually gets consulted in daily work.
Equally important is active maintenance in the repository and a gradual rollout on existing codebases. A style guide that gets versioned with the code and only adds new rules together with a rationale stays relevant for years, instead of going stale after a few months.
Creating a TypeScript style guide, the essentials at a glance
Limit the scope
Only govern decisions Prettier cannot make automatically, otherwise the guide becomes unwieldy.
Enforce automatically
Every rule needs an ESLint counterpart, otherwise it vanishes from daily practice within weeks.
Examples over prose
Short before and after code examples from real project situations instead of long text sections.
Maintain in the repository
The guide lives next to the code and grows in sync with every new ESLint rule.